From 0bf1ce2dc9e029249d37a018512da130ec88b94e Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Thu, 24 Jul 2025 18:24:08 +0200 Subject: [PATCH 1/5] Add support for new stream commands - Add support for xackdel and xdelex - Extend xadd and xtrim - Add relevant tests --- .../redis/clients/jedis/BuilderFactory.java | 36 + .../redis/clients/jedis/CommandObjects.java | 32 + src/main/java/redis/clients/jedis/Jedis.java | 48 + .../redis/clients/jedis/PipeliningBase.java | 40 + .../java/redis/clients/jedis/Protocol.java | 2 +- .../redis/clients/jedis/UnifiedJedis.java | 40 + .../clients/jedis/args/StreamTrimMode.java | 38 + .../jedis/commands/StreamBinaryCommands.java | 22 + .../jedis/commands/StreamCommands.java | 29 + .../StreamPipelineBinaryCommands.java | 10 + .../commands/StreamPipelineCommands.java | 21 + .../clients/jedis/params/XAddParams.java | 23 +- .../clients/jedis/params/XTrimParams.java | 22 +- .../clients/jedis/resps/StreamTrimResult.java | 82 ++ .../jedis/StreamsBinaryCommandsTest.java | 257 ++++++ .../commands/jedis/StreamsCommandsTest.java | 311 ++++++- .../StreamsBinaryCommandsTestBase.java | 281 ++++++ .../unified/StreamsCommandsTestBase.java | 846 ++++++++++++++++++ .../cluster/ClusterStreamsCommandsTest.java | 28 + .../pooled/PooledStreamsCommandsTest.java | 28 + .../resps/StreamEntryDeletionResultTest.java | 48 + 21 files changed, 2235 insertions(+), 9 deletions(-) create mode 100644 src/main/java/redis/clients/jedis/args/StreamTrimMode.java create mode 100644 src/main/java/redis/clients/jedis/resps/StreamTrimResult.java create mode 100644 src/test/java/redis/clients/jedis/commands/unified/StreamsCommandsTestBase.java create mode 100644 src/test/java/redis/clients/jedis/commands/unified/cluster/ClusterStreamsCommandsTest.java create mode 100644 src/test/java/redis/clients/jedis/commands/unified/pooled/PooledStreamsCommandsTest.java create mode 100644 src/test/java/redis/clients/jedis/resps/StreamEntryDeletionResultTest.java diff --git a/src/main/java/redis/clients/jedis/BuilderFactory.java b/src/main/java/redis/clients/jedis/BuilderFactory.java index 15bb0122cd..d3a3455c2b 100644 --- a/src/main/java/redis/clients/jedis/BuilderFactory.java +++ b/src/main/java/redis/clients/jedis/BuilderFactory.java @@ -1261,6 +1261,42 @@ public List build(Object data) { } }; + public static final Builder STREAM_ENTRY_DELETION_RESULT = new Builder() { + @Override + public StreamTrimResult build(Object data) { + if (data == null) { + return null; + } + return StreamTrimResult.fromLong((Long) data); + } + + @Override + public String toString() { + return "StreamEntryDeletionResult"; + } + }; + + public static final Builder> STREAM_ENTRY_DELETION_RESULT_LIST = new Builder>() { + @Override + @SuppressWarnings("unchecked") + public List build(Object data) { + if (data == null) { + return null; + } + List objectList = (List) data; + List responses = new ArrayList<>(objectList.size()); + for (Object object : objectList) { + responses.add(STREAM_ENTRY_DELETION_RESULT.build(object)); + } + return responses; + } + + @Override + public String toString() { + return "List"; + } + }; + public static final Builder STREAM_ENTRY = new Builder() { @Override @SuppressWarnings("unchecked") diff --git a/src/main/java/redis/clients/jedis/CommandObjects.java b/src/main/java/redis/clients/jedis/CommandObjects.java index ea4930f894..7f43f44bf0 100644 --- a/src/main/java/redis/clients/jedis/CommandObjects.java +++ b/src/main/java/redis/clients/jedis/CommandObjects.java @@ -2626,10 +2626,26 @@ public final CommandObject xack(String key, String group, StreamEntryID... return new CommandObject<>(commandArguments(XACK).key(key).add(group).addObjects((Object[]) ids), BuilderFactory.LONG); } + public final CommandObject> xackdel(String key, String group, StreamEntryID... ids) { + return new CommandObject<>(commandArguments(XACKDEL).key(key).add(group).add("IDS").add(ids.length).addObjects((Object[]) ids), BuilderFactory.STREAM_ENTRY_DELETION_RESULT_LIST); + } + + public final CommandObject> xackdel(String key, String group, StreamTrimMode trimMode, StreamEntryID... ids) { + return new CommandObject<>(commandArguments(XACKDEL).key(key).add(group).add(trimMode).add("IDS").add(ids.length).addObjects((Object[]) ids), BuilderFactory.STREAM_ENTRY_DELETION_RESULT_LIST); + } + public final CommandObject xack(byte[] key, byte[] group, byte[]... ids) { return new CommandObject<>(commandArguments(XACK).key(key).add(group).addObjects((Object[]) ids), BuilderFactory.LONG); } + public final CommandObject> xackdel(byte[] key, byte[] group, byte[]... ids) { + return new CommandObject<>(commandArguments(XACKDEL).key(key).add(group).add("IDS").add(ids.length).addObjects((Object[]) ids), BuilderFactory.STREAM_ENTRY_DELETION_RESULT_LIST); + } + + public final CommandObject> xackdel(byte[] key, byte[] group, StreamTrimMode trimMode, byte[]... ids) { + return new CommandObject<>(commandArguments(XACKDEL).key(key).add(group).add(trimMode).add("IDS").add(ids.length).addObjects((Object[]) ids), BuilderFactory.STREAM_ENTRY_DELETION_RESULT_LIST); + } + public final CommandObject xgroupCreate(String key, String groupName, StreamEntryID id, boolean makeStream) { CommandArguments args = commandArguments(XGROUP).add(CREATE).key(key) .add(groupName).add(id == null ? "0-0" : id); @@ -2687,6 +2703,14 @@ public final CommandObject xdel(String key, StreamEntryID... ids) { return new CommandObject<>(commandArguments(XDEL).key(key).addObjects((Object[]) ids), BuilderFactory.LONG); } + public final CommandObject> xdelex(String key, StreamEntryID... ids) { + return new CommandObject<>(commandArguments(XDELEX).key(key).add("IDS").add(ids.length).addObjects((Object[]) ids), BuilderFactory.STREAM_ENTRY_DELETION_RESULT_LIST); + } + + public final CommandObject> xdelex(String key, StreamTrimMode trimMode, StreamEntryID... ids) { + return new CommandObject<>(commandArguments(XDELEX).key(key).add(trimMode).add("IDS").add(ids.length).addObjects((Object[]) ids), BuilderFactory.STREAM_ENTRY_DELETION_RESULT_LIST); + } + public final CommandObject xtrim(String key, long maxLen, boolean approximate) { CommandArguments args = commandArguments(XTRIM).key(key).add(MAXLEN); if (approximate) args.add(Protocol.BYTES_TILDE); @@ -2702,6 +2726,14 @@ public final CommandObject xdel(byte[] key, byte[]... ids) { return new CommandObject<>(commandArguments(XDEL).key(key).addObjects((Object[]) ids), BuilderFactory.LONG); } + public final CommandObject> xdelex(byte[] key, byte[]... ids) { + return new CommandObject<>(commandArguments(XDELEX).key(key).add("IDS").add(ids.length).addObjects((Object[]) ids), BuilderFactory.STREAM_ENTRY_DELETION_RESULT_LIST); + } + + public final CommandObject> xdelex(byte[] key, StreamTrimMode trimMode, byte[]... ids) { + return new CommandObject<>(commandArguments(XDELEX).key(key).add(trimMode).add("IDS").add(ids.length).addObjects((Object[]) ids), BuilderFactory.STREAM_ENTRY_DELETION_RESULT_LIST); + } + public final CommandObject xtrim(byte[] key, long maxLen, boolean approximateLength) { CommandArguments args = commandArguments(XTRIM).key(key).add(MAXLEN); if (approximateLength) args.add(Protocol.BYTES_TILDE); diff --git a/src/main/java/redis/clients/jedis/Jedis.java b/src/main/java/redis/clients/jedis/Jedis.java index e19a4fa619..b92da0a5ca 100644 --- a/src/main/java/redis/clients/jedis/Jedis.java +++ b/src/main/java/redis/clients/jedis/Jedis.java @@ -4866,6 +4866,18 @@ public long xack(byte[] key, byte[] group, byte[]... ids) { return connection.executeCommand(commandObjects.xack(key, group, ids)); } + @Override + public List xackdel(byte[] key, byte[] group, byte[]... ids) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.xackdel(key, group, ids)); + } + + @Override + public List xackdel(byte[] key, byte[] group, StreamTrimMode trimMode, byte[]... ids) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.xackdel(key, group, trimMode, ids)); + } + @Override public String xgroupCreate(byte[] key, byte[] consumer, byte[] id, boolean makeStream) { checkIsInMultiOrPipeline(); @@ -4902,6 +4914,18 @@ public long xdel(byte[] key, byte[]... ids) { return connection.executeCommand(commandObjects.xdel(key, ids)); } + @Override + public List xdelex(byte[] key, byte[]... ids) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.xdelex(key, ids)); + } + + @Override + public List xdelex(byte[] key, StreamTrimMode trimMode, byte[]... ids) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.xdelex(key, trimMode, ids)); + } + @Override public long xtrim(byte[] key, long maxLen, boolean approximateLength) { checkIsInMultiOrPipeline(); @@ -9677,6 +9701,18 @@ public long xack(final String key, final String group, final StreamEntryID... id return connection.executeCommand(commandObjects.xack(key, group, ids)); } + @Override + public List xackdel(final String key, final String group, final StreamEntryID... ids) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.xackdel(key, group, ids)); + } + + @Override + public List xackdel(final String key, final String group, final StreamTrimMode trimMode, final StreamEntryID... ids) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.xackdel(key, group, trimMode, ids)); + } + @Override public String xgroupCreate(final String key, final String groupName, final StreamEntryID id, final boolean makeStream) { @@ -9714,6 +9750,18 @@ public long xdel(final String key, final StreamEntryID... ids) { return connection.executeCommand(commandObjects.xdel(key, ids)); } + @Override + public List xdelex(final String key, final StreamEntryID... ids) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.xdelex(key, ids)); + } + + @Override + public List xdelex(final String key, final StreamTrimMode trimMode, final StreamEntryID... ids) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.xdelex(key, trimMode, ids)); + } + @Override public long xtrim(final String key, final long maxLen, final boolean approximateLength) { checkIsInMultiOrPipeline(); diff --git a/src/main/java/redis/clients/jedis/PipeliningBase.java b/src/main/java/redis/clients/jedis/PipeliningBase.java index f7b974f552..dc2481c62e 100644 --- a/src/main/java/redis/clients/jedis/PipeliningBase.java +++ b/src/main/java/redis/clients/jedis/PipeliningBase.java @@ -1552,6 +1552,16 @@ public Response xack(String key, String group, StreamEntryID... ids) { return appendCommand(commandObjects.xack(key, group, ids)); } + @Override + public Response> xackdel(String key, String group, StreamEntryID... ids) { + return appendCommand(commandObjects.xackdel(key, group, ids)); + } + + @Override + public Response> xackdel(String key, String group, StreamTrimMode trimMode, StreamEntryID... ids) { + return appendCommand(commandObjects.xackdel(key, group, trimMode, ids)); + } + @Override public Response xgroupCreate(String key, String groupName, StreamEntryID id, boolean makeStream) { return appendCommand(commandObjects.xgroupCreate(key, groupName, id, makeStream)); @@ -1592,6 +1602,16 @@ public Response xdel(String key, StreamEntryID... ids) { return appendCommand(commandObjects.xdel(key, ids)); } + @Override + public Response> xdelex(String key, StreamEntryID... ids) { + return appendCommand(commandObjects.xdelex(key, ids)); + } + + @Override + public Response> xdelex(String key, StreamTrimMode trimMode, StreamEntryID... ids) { + return appendCommand(commandObjects.xdelex(key, trimMode, ids)); + } + @Override public Response xtrim(String key, long maxLen, boolean approximate) { return appendCommand(commandObjects.xtrim(key, maxLen, approximate)); @@ -3264,6 +3284,16 @@ public Response xack(byte[] key, byte[] group, byte[]... ids) { return appendCommand(commandObjects.xack(key, group, ids)); } + @Override + public Response> xackdel(byte[] key, byte[] group, byte[]... ids) { + return appendCommand(commandObjects.xackdel(key, group, ids)); + } + + @Override + public Response> xackdel(byte[] key, byte[] group, StreamTrimMode trimMode, byte[]... ids) { + return appendCommand(commandObjects.xackdel(key, group, trimMode, ids)); + } + @Override public Response xgroupCreate(byte[] key, byte[] groupName, byte[] id, boolean makeStream) { return appendCommand(commandObjects.xgroupCreate(key, groupName, id, makeStream)); @@ -3294,6 +3324,16 @@ public Response xdel(byte[] key, byte[]... ids) { return appendCommand(commandObjects.xdel(key, ids)); } + @Override + public Response> xdelex(byte[] key, byte[]... ids) { + return appendCommand(commandObjects.xdelex(key, ids)); + } + + @Override + public Response> xdelex(byte[] key, StreamTrimMode trimMode, byte[]... ids) { + return appendCommand(commandObjects.xdelex(key, trimMode, ids)); + } + @Override public Response xtrim(byte[] key, long maxLen, boolean approximateLength) { return appendCommand(commandObjects.xtrim(key, maxLen, approximateLength)); diff --git a/src/main/java/redis/clients/jedis/Protocol.java b/src/main/java/redis/clients/jedis/Protocol.java index 6d59a8b913..226702cd9f 100644 --- a/src/main/java/redis/clients/jedis/Protocol.java +++ b/src/main/java/redis/clients/jedis/Protocol.java @@ -305,7 +305,7 @@ public static enum Command implements ProtocolCommand { GEORADIUSBYMEMBER, GEORADIUSBYMEMBER_RO, // <-- geo PFADD, PFCOUNT, PFMERGE, // <-- hyper log log XADD, XLEN, XDEL, XTRIM, XRANGE, XREVRANGE, XREAD, XACK, XGROUP, XREADGROUP, XPENDING, XCLAIM, - XAUTOCLAIM, XINFO, // <-- stream + XAUTOCLAIM, XINFO, XDELEX, XACKDEL, // <-- stream EVAL, EVALSHA, SCRIPT, EVAL_RO, EVALSHA_RO, FUNCTION, FCALL, FCALL_RO, // <-- program SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBLISH, PUBSUB, SSUBSCRIBE, SUNSUBSCRIBE, SPUBLISH, // <-- pub sub diff --git a/src/main/java/redis/clients/jedis/UnifiedJedis.java b/src/main/java/redis/clients/jedis/UnifiedJedis.java index e3960862fa..d94a075c7b 100644 --- a/src/main/java/redis/clients/jedis/UnifiedJedis.java +++ b/src/main/java/redis/clients/jedis/UnifiedJedis.java @@ -3201,6 +3201,16 @@ public long xack(String key, String group, StreamEntryID... ids) { return executeCommand(commandObjects.xack(key, group, ids)); } + @Override + public List xackdel(String key, String group, StreamEntryID... ids) { + return executeCommand(commandObjects.xackdel(key, group, ids)); + } + + @Override + public List xackdel(String key, String group, StreamTrimMode trimMode, StreamEntryID... ids) { + return executeCommand(commandObjects.xackdel(key, group, trimMode, ids)); + } + @Override public String xgroupCreate(String key, String groupName, StreamEntryID id, boolean makeStream) { return executeCommand(commandObjects.xgroupCreate(key, groupName, id, makeStream)); @@ -3241,6 +3251,16 @@ public long xdel(String key, StreamEntryID... ids) { return executeCommand(commandObjects.xdel(key, ids)); } + @Override + public List xdelex(String key, StreamEntryID... ids) { + return executeCommand(commandObjects.xdelex(key, ids)); + } + + @Override + public List xdelex(String key, StreamTrimMode trimMode, StreamEntryID... ids) { + return executeCommand(commandObjects.xdelex(key, trimMode, ids)); + } + @Override public long xtrim(String key, long maxLen, boolean approximate) { return executeCommand(commandObjects.xtrim(key, maxLen, approximate)); @@ -3356,6 +3376,16 @@ public long xack(byte[] key, byte[] group, byte[]... ids) { return executeCommand(commandObjects.xack(key, group, ids)); } + @Override + public List xackdel(byte[] key, byte[] group, byte[]... ids) { + return executeCommand(commandObjects.xackdel(key, group, ids)); + } + + @Override + public List xackdel(byte[] key, byte[] group, StreamTrimMode trimMode, byte[]... ids) { + return executeCommand(commandObjects.xackdel(key, group, trimMode, ids)); + } + @Override public String xgroupCreate(byte[] key, byte[] groupName, byte[] id, boolean makeStream) { return executeCommand(commandObjects.xgroupCreate(key, groupName, id, makeStream)); @@ -3386,6 +3416,16 @@ public long xdel(byte[] key, byte[]... ids) { return executeCommand(commandObjects.xdel(key, ids)); } + @Override + public List xdelex(byte[] key, byte[]... ids) { + return executeCommand(commandObjects.xdelex(key, ids)); + } + + @Override + public List xdelex(byte[] key, StreamTrimMode trimMode, byte[]... ids) { + return executeCommand(commandObjects.xdelex(key, trimMode, ids)); + } + @Override public long xtrim(byte[] key, long maxLen, boolean approximateLength) { return executeCommand(commandObjects.xtrim(key, maxLen, approximateLength)); diff --git a/src/main/java/redis/clients/jedis/args/StreamTrimMode.java b/src/main/java/redis/clients/jedis/args/StreamTrimMode.java new file mode 100644 index 0000000000..b17c06f7dd --- /dev/null +++ b/src/main/java/redis/clients/jedis/args/StreamTrimMode.java @@ -0,0 +1,38 @@ +package redis.clients.jedis.args; + +import redis.clients.jedis.util.SafeEncoder; + +/** + * Trim strategy for stream commands that handle consumer group references. + * Used with XDELEX, XACKDEL, and enhanced XADD/XTRIM commands. + */ +public enum StreamTrimMode implements Rawable { + + /** + * Preserves existing references to entries in all consumer groups' PEL. + * This is the default behavior similar to XDEL. + */ + KEEP_REFERENCES("KEEPREF"), + + /** + * Removes all references to entries from all consumer groups' pending entry lists, + * effectively cleaning up all traces of the messages. + */ + DELETE_REFERENCES("DELREF"), + + /** + * Only operates on entries that were read and acknowledged by all consumer groups. + */ + ACKNOWLEDGED("ACKED"); + + private final byte[] raw; + + StreamTrimMode(String redisParamName) { + raw = SafeEncoder.encode(redisParamName); + } + + @Override + public byte[] getRaw() { + return raw; + } +} diff --git a/src/main/java/redis/clients/jedis/commands/StreamBinaryCommands.java b/src/main/java/redis/clients/jedis/commands/StreamBinaryCommands.java index 5db025ef2d..b4b3f3dcad 100644 --- a/src/main/java/redis/clients/jedis/commands/StreamBinaryCommands.java +++ b/src/main/java/redis/clients/jedis/commands/StreamBinaryCommands.java @@ -4,8 +4,10 @@ import java.util.Map; import redis.clients.jedis.StreamEntryID; +import redis.clients.jedis.args.StreamTrimMode; import redis.clients.jedis.params.*; import redis.clients.jedis.resps.StreamEntryBinary; +import redis.clients.jedis.resps.StreamTrimResult; public interface StreamBinaryCommands { @@ -27,6 +29,16 @@ default byte[] xadd(byte[] key, Map hash, XAddParams params) { long xack(byte[] key, byte[] group, byte[]... ids); + /** + * XACKDEL key group [KEEPREF | DELREF | ACKED] IDS numids id [id ...] + */ + List xackdel(byte[] key, byte[] group, byte[]... ids); + + /** + * XACKDEL key group [KEEPREF | DELREF | ACKED] IDS numids id [id ...] + */ + List xackdel(byte[] key, byte[] group, StreamTrimMode trimMode, byte[]... ids); + String xgroupCreate(byte[] key, byte[] groupName, byte[] id, boolean makeStream); String xgroupSetID(byte[] key, byte[] groupName, byte[] id); @@ -39,6 +51,16 @@ default byte[] xadd(byte[] key, Map hash, XAddParams params) { long xdel(byte[] key, byte[]... ids); + /** + * XDELEX key [KEEPREF | DELREF | ACKED] IDS numids id [id ...] + */ + List xdelex(byte[] key, byte[]... ids); + + /** + * XDELEX key [KEEPREF | DELREF | ACKED] IDS numids id [id ...] + */ + List xdelex(byte[] key, StreamTrimMode trimMode, byte[]... ids); + long xtrim(byte[] key, long maxLen, boolean approximateLength); long xtrim(byte[] key, XTrimParams params); diff --git a/src/main/java/redis/clients/jedis/commands/StreamCommands.java b/src/main/java/redis/clients/jedis/commands/StreamCommands.java index 163e11050e..5ba92ca9f7 100644 --- a/src/main/java/redis/clients/jedis/commands/StreamCommands.java +++ b/src/main/java/redis/clients/jedis/commands/StreamCommands.java @@ -4,6 +4,7 @@ import java.util.Map; import redis.clients.jedis.StreamEntryID; +import redis.clients.jedis.args.StreamTrimMode; import redis.clients.jedis.params.*; import redis.clients.jedis.resps.*; @@ -98,6 +99,20 @@ default StreamEntryID xadd(String key, Map hash, XAddParams para */ long xack(String key, String group, StreamEntryID... ids); + /** + * XACKDEL key group [KEEPREF | DELREF | ACKED] IDS numids id [id ...] + * Combines XACK and XDEL functionalities. Acknowledges specified message IDs + * in the given consumer group and attempts to delete corresponding stream entries. + */ + List xackdel(String key, String group, StreamEntryID... ids); + + /** + * XACKDEL key group [KEEPREF | DELREF | ACKED] IDS numids id [id ...] + * Combines XACK and XDEL functionalities. Acknowledges specified message IDs + * in the given consumer group and attempts to delete corresponding stream entries. + */ + List xackdel(String key, String group, StreamTrimMode trimMode, StreamEntryID... ids); + /** * {@code XGROUP CREATE key groupName } */ @@ -128,6 +143,20 @@ default StreamEntryID xadd(String key, Map hash, XAddParams para */ long xdel(String key, StreamEntryID... ids); + /** + * XDELEX key [KEEPREF | DELREF | ACKED] IDS numids id [id ...] + * Extended XDEL command with enhanced control over message entry deletion + * with respect to consumer groups. + */ + List xdelex(String key, StreamEntryID... ids); + + /** + * XDELEX key [KEEPREF | DELREF | ACKED] IDS numids id [id ...] + * Extended XDEL command with enhanced control over message entry deletion + * with respect to consumer groups. + */ + List xdelex(String key, StreamTrimMode trimMode, StreamEntryID... ids); + /** * XTRIM key MAXLEN [~] count */ diff --git a/src/main/java/redis/clients/jedis/commands/StreamPipelineBinaryCommands.java b/src/main/java/redis/clients/jedis/commands/StreamPipelineBinaryCommands.java index 3cda8f079a..f863f8cfb1 100644 --- a/src/main/java/redis/clients/jedis/commands/StreamPipelineBinaryCommands.java +++ b/src/main/java/redis/clients/jedis/commands/StreamPipelineBinaryCommands.java @@ -5,8 +5,10 @@ import redis.clients.jedis.Response; import redis.clients.jedis.StreamEntryID; +import redis.clients.jedis.args.StreamTrimMode; import redis.clients.jedis.params.*; import redis.clients.jedis.resps.StreamEntryBinary; +import redis.clients.jedis.resps.StreamTrimResult; public interface StreamPipelineBinaryCommands { @@ -28,6 +30,10 @@ default Response xadd(byte[] key, Map hash, XAddParams p Response xack(byte[] key, byte[] group, byte[]... ids); + Response> xackdel(byte[] key, byte[] group, byte[]... ids); + + Response> xackdel(byte[] key, byte[] group, StreamTrimMode trimMode, byte[]... ids); + Response xgroupCreate(byte[] key, byte[] groupName, byte[] id, boolean makeStream); Response xgroupSetID(byte[] key, byte[] groupName, byte[] id); @@ -40,6 +46,10 @@ default Response xadd(byte[] key, Map hash, XAddParams p Response xdel(byte[] key, byte[]... ids); + Response> xdelex(byte[] key, byte[]... ids); + + Response> xdelex(byte[] key, StreamTrimMode trimMode, byte[]... ids); + Response xtrim(byte[] key, long maxLen, boolean approximateLength); Response xtrim(byte[] key, XTrimParams params); diff --git a/src/main/java/redis/clients/jedis/commands/StreamPipelineCommands.java b/src/main/java/redis/clients/jedis/commands/StreamPipelineCommands.java index d4bda0fb98..5b37669f19 100644 --- a/src/main/java/redis/clients/jedis/commands/StreamPipelineCommands.java +++ b/src/main/java/redis/clients/jedis/commands/StreamPipelineCommands.java @@ -5,6 +5,7 @@ import redis.clients.jedis.Response; import redis.clients.jedis.StreamEntryID; +import redis.clients.jedis.args.StreamTrimMode; import redis.clients.jedis.params.*; import redis.clients.jedis.resps.*; @@ -91,6 +92,16 @@ default Response xadd(String key, Map hash, XAddP */ Response xack(String key, String group, StreamEntryID... ids); + /** + * XACKDEL key group [KEEPREF | DELREF | ACKED] IDS numids id [id ...] + */ + Response> xackdel(String key, String group, StreamEntryID... ids); + + /** + * XACKDEL key group [KEEPREF | DELREF | ACKED] IDS numids id [id ...] + */ + Response> xackdel(String key, String group, StreamTrimMode trimMode, StreamEntryID... ids); + /** * {@code XGROUP CREATE key groupName } */ @@ -131,6 +142,16 @@ default Response xadd(String key, Map hash, XAddP */ Response xdel(String key, StreamEntryID... ids); + /** + * XDELEX key [KEEPREF | DELREF | ACKED] IDS numids id [id ...] + */ + Response> xdelex(String key, StreamEntryID... ids); + + /** + * XDELEX key [KEEPREF | DELREF | ACKED] IDS numids id [id ...] + */ + Response> xdelex(String key, StreamTrimMode trimMode, StreamEntryID... ids); + /** * XTRIM key MAXLEN [~] count */ diff --git a/src/main/java/redis/clients/jedis/params/XAddParams.java b/src/main/java/redis/clients/jedis/params/XAddParams.java index 3575c6e5d2..f4e294e82b 100644 --- a/src/main/java/redis/clients/jedis/params/XAddParams.java +++ b/src/main/java/redis/clients/jedis/params/XAddParams.java @@ -6,6 +6,7 @@ import redis.clients.jedis.StreamEntryID; import redis.clients.jedis.args.Rawable; import redis.clients.jedis.args.RawableFactory; +import redis.clients.jedis.args.StreamTrimMode; import java.util.Objects; @@ -25,6 +26,8 @@ public class XAddParams implements IParams { private Long limit; + private StreamTrimMode trimMode; + public static XAddParams xAddParams() { return new XAddParams(); } @@ -81,6 +84,17 @@ public XAddParams limit(long limit) { return this; } + /** + * When trimming, defines desired behaviour for handling consumer group references. + * see {@link StreamTrimMode} for details. + * + * @return XAddParams + */ + public XAddParams trimmingMode(StreamTrimMode trimMode) { + this.trimMode = trimMode; + return this; + } + @Override public void addParams(CommandArguments args) { @@ -114,6 +128,10 @@ public void addParams(CommandArguments args) { args.add(Keyword.LIMIT).add(limit); } + if (trimMode != null) { + args.add(trimMode); + } + args.add(id != null ? id : StreamEntryID.NEW_ENTRY); } @@ -122,11 +140,12 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; XAddParams that = (XAddParams) o; - return approximateTrimming == that.approximateTrimming && exactTrimming == that.exactTrimming && nomkstream == that.nomkstream && Objects.equals(id, that.id) && Objects.equals(maxLen, that.maxLen) && Objects.equals(minId, that.minId) && Objects.equals(limit, that.limit); + return approximateTrimming == that.approximateTrimming && exactTrimming == that.exactTrimming && nomkstream == that.nomkstream && Objects.equals(id, that.id) && Objects.equals(maxLen, that.maxLen) && Objects.equals(minId, that.minId) && Objects.equals(limit, that.limit) && trimMode == that.trimMode; } @Override public int hashCode() { - return Objects.hash(id, maxLen, approximateTrimming, exactTrimming, nomkstream, minId, limit); + return Objects.hash(id, maxLen, approximateTrimming, exactTrimming, nomkstream, minId, limit, + trimMode); } } diff --git a/src/main/java/redis/clients/jedis/params/XTrimParams.java b/src/main/java/redis/clients/jedis/params/XTrimParams.java index af77ba81b3..d6487388f4 100644 --- a/src/main/java/redis/clients/jedis/params/XTrimParams.java +++ b/src/main/java/redis/clients/jedis/params/XTrimParams.java @@ -3,6 +3,7 @@ import redis.clients.jedis.CommandArguments; import redis.clients.jedis.Protocol; import redis.clients.jedis.Protocol.Keyword; +import redis.clients.jedis.args.StreamTrimMode; import java.util.Objects; @@ -18,6 +19,8 @@ public class XTrimParams implements IParams { private Long limit; + private StreamTrimMode trimMode; + public static XTrimParams xTrimParams() { return new XTrimParams(); } @@ -48,6 +51,17 @@ public XTrimParams limit(long limit) { return this; } + /** + * Defines desired behaviour for handling consumer group references. + * see {@link StreamTrimMode} for details. + * + * @return XAddParams + */ + public XTrimParams trimmingMode(StreamTrimMode trimMode) { + this.trimMode = trimMode; + return this; + } + @Override public void addParams(CommandArguments args) { if (maxLen != null) { @@ -75,6 +89,10 @@ public void addParams(CommandArguments args) { if (limit != null) { args.add(Keyword.LIMIT).add(limit); } + + if (trimMode != null) { + args.add(trimMode); + } } @Override @@ -82,11 +100,11 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; XTrimParams that = (XTrimParams) o; - return approximateTrimming == that.approximateTrimming && exactTrimming == that.exactTrimming && Objects.equals(maxLen, that.maxLen) && Objects.equals(minId, that.minId) && Objects.equals(limit, that.limit); + return approximateTrimming == that.approximateTrimming && exactTrimming == that.exactTrimming && Objects.equals(maxLen, that.maxLen) && Objects.equals(minId, that.minId) && Objects.equals(limit, that.limit) && trimMode == that.trimMode; } @Override public int hashCode() { - return Objects.hash(maxLen, approximateTrimming, exactTrimming, minId, limit); + return Objects.hash(maxLen, approximateTrimming, exactTrimming, minId, limit, trimMode); } } diff --git a/src/main/java/redis/clients/jedis/resps/StreamTrimResult.java b/src/main/java/redis/clients/jedis/resps/StreamTrimResult.java new file mode 100644 index 0000000000..72e158eab7 --- /dev/null +++ b/src/main/java/redis/clients/jedis/resps/StreamTrimResult.java @@ -0,0 +1,82 @@ +package redis.clients.jedis.resps; + +/** + * Represents the result of a stream entry deletion operation for XDELEX and XACKDEL commands. + * - NOT_FOUND (-1): ID doesn't exist in stream + * - DELETED (1): Entry was deleted/acknowledged and deleted + * - ACKNOWLEDGED_NOT_DELETED (2): Entry was acknowledged but not deleted (still has dangling references) + */ +public enum StreamTrimResult { + + /** + * The stream entry ID doesn't exist in the stream. + * Returned when trying to delete/acknowledge a non-existent entry. + */ + NOT_FOUND(-1), + + /** + * The entry was successfully deleted/acknowledged and deleted. + * This is the typical successful case. + */ + DELETED(1), + + /** + * The entry was acknowledged but not deleted because it still has dangling references + * in other consumer groups' pending entry lists. + */ + ACKNOWLEDGED_NOT_DELETED(2); + + private final int code; + + StreamTrimResult(int code) { + this.code = code; + } + + /** + * Gets the numeric code returned by Redis for this result. + * + * @return the numeric code (-1, 1, or 2) + */ + public int getCode() { + return code; + } + + /** + * Creates a StreamEntryDeletionResult from the numeric code returned by Redis. + * + * @param code the numeric code from Redis + * @return the corresponding StreamEntryDeletionResult + * @throws IllegalArgumentException if the code is not recognized + */ + public static StreamTrimResult fromCode(int code) { + switch (code) { + case -1: + return NOT_FOUND; + case 1: + return DELETED; + case 2: + return ACKNOWLEDGED_NOT_DELETED; + default: + throw new IllegalArgumentException("Unknown stream entry deletion result code: " + code); + } + } + + /** + * Creates a StreamEntryDeletionResult from a Long value returned by Redis. + * + * @param value the Long value from Redis + * @return the corresponding StreamEntryDeletionResult + * @throws IllegalArgumentException if the value is null or not recognized + */ + public static StreamTrimResult fromLong(Long value) { + if (value == null) { + throw new IllegalArgumentException("Stream entry deletion result value cannot be null"); + } + return fromCode(value.intValue()); + } + + @Override + public String toString() { + return name() + "(" + code + ")"; + } +} diff --git a/src/test/java/redis/clients/jedis/commands/jedis/StreamsBinaryCommandsTest.java b/src/test/java/redis/clients/jedis/commands/jedis/StreamsBinaryCommandsTest.java index e53b4d87ba..8034453388 100644 --- a/src/test/java/redis/clients/jedis/commands/jedis/StreamsBinaryCommandsTest.java +++ b/src/test/java/redis/clients/jedis/commands/jedis/StreamsBinaryCommandsTest.java @@ -6,11 +6,14 @@ import org.junit.jupiter.params.provider.MethodSource; import redis.clients.jedis.RedisProtocol; import redis.clients.jedis.StreamEntryID; +import redis.clients.jedis.args.StreamTrimMode; import redis.clients.jedis.exceptions.JedisDataException; import redis.clients.jedis.params.XAddParams; import redis.clients.jedis.params.XReadGroupParams; import redis.clients.jedis.params.XReadParams; +import redis.clients.jedis.params.XTrimParams; import redis.clients.jedis.resps.StreamEntryBinary; +import redis.clients.jedis.resps.StreamTrimResult; import java.util.ArrayList; import java.util.HashMap; @@ -21,7 +24,10 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasSize; import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static redis.clients.jedis.StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY; import static redis.clients.jedis.util.StreamEntryBinaryListMatcher.equalsStreamEntries; @@ -253,4 +259,255 @@ public void xreadGroupBinaryAsMapMultipleStreams() { assertThat(actualEntries.get(STREAM_KEY_2), equalsStreamEntries(stream2Entries)); } + // ========== XACKDEL Command Tests ========== + + @Test + public void testXackdel() { + // Add a message to the stream + byte[] messageId = jedis.xadd(STREAM_KEY_1, new XAddParams().id("1-0"), HASH_1); + assertNotNull(messageId); + assertEquals(1L, jedis.xlen(STREAM_KEY_1)); + + // Read the message with consumer group to add it to PEL + Map streams = offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY); + List>> messages = jedis.xreadGroupBinary( + GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(1), streams); + + assertEquals(1, messages.size()); + assertEquals(1, messages.get(0).getValue().size()); + byte[] readMessageId = messages.get(0).getValue().get(0).getID().toString().getBytes(); + + // Test XACKDEL - should acknowledge and delete the message + List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, readMessageId); + assertThat(results, hasSize(1)); + assertEquals(StreamTrimResult.DELETED, results.get(0)); + + // Verify message is deleted from stream + assertEquals(0L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void testXackdelWithTrimMode() { + // Add multiple messages + jedis.xadd(STREAM_KEY_1, new XAddParams().id("1-0"), HASH_1); + jedis.xadd(STREAM_KEY_1, new XAddParams().id("2-0"), HASH_2); + assertEquals(2L, jedis.xlen(STREAM_KEY_1)); + + // Read the messages with consumer group + Map streams = offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY); + List>> messages = jedis.xreadGroupBinary( + GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(2), streams); + + assertEquals(1, messages.size()); + assertEquals(2, messages.get(0).getValue().size()); + + // Test XACKDEL with KEEP_REFERENCES mode + byte[] readId1 = messages.get(0).getValue().get(0).getID().toString().getBytes(); + List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, StreamTrimMode.KEEP_REFERENCES, readId1); + assertThat(results, hasSize(1)); + assertEquals(StreamTrimResult.DELETED, results.get(0)); + + // Verify one message is deleted + assertEquals(1L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void testXackdelUnreadMessages() { + // Add test entries but don't read them + byte[] id1 = jedis.xadd(STREAM_KEY_1, new XAddParams().id("1-0"), HASH_1); + + // Test XACKDEL on unread messages - should return NOT_FOUND for PEL + List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, id1); + + assertThat(results, hasSize(1)); + // Should return NOT_FOUND because message was never read by the consumer group + assertEquals(StreamTrimResult.NOT_FOUND, results.get(0)); + + // Stream should still contain the message + assertEquals(1L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void testXackdelMultipleMessages() { + // Add multiple messages + jedis.xadd(STREAM_KEY_1, new XAddParams().id("1-0"), HASH_1); + jedis.xadd(STREAM_KEY_1, new XAddParams().id("2-0"), HASH_2); + jedis.xadd(STREAM_KEY_1, new XAddParams().id("3-0"), HASH_1); + assertEquals(3L, jedis.xlen(STREAM_KEY_1)); + + // Read the messages with consumer group + Map streams = offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY); + List>> messages = jedis.xreadGroupBinary( + GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3), streams); + + assertEquals(1, messages.size()); + assertEquals(3, messages.get(0).getValue().size()); + + // Test XACKDEL with multiple IDs + byte[] readId1 = messages.get(0).getValue().get(0).getID().toString().getBytes(); + byte[] readId2 = messages.get(0).getValue().get(1).getID().toString().getBytes(); + List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, readId1, readId2); + assertThat(results, hasSize(2)); + assertEquals(StreamTrimResult.DELETED, results.get(0)); + assertEquals(StreamTrimResult.DELETED, results.get(1)); + + // Verify two messages are deleted + assertEquals(1L, jedis.xlen(STREAM_KEY_1)); + } + + // ========== XDELEX Command Tests ========== + + @Test + public void testXdelex() { + // Add test entries + byte[] id1 = jedis.xadd(STREAM_KEY_1, new XAddParams().id("1-0"), HASH_1); + byte[] id2 = jedis.xadd(STREAM_KEY_1, new XAddParams().id("2-0"), HASH_2); + assertEquals(2L, jedis.xlen(STREAM_KEY_1)); + + // Test basic XDELEX without parameters (should behave like XDEL with KEEP_REFERENCES) + List results = jedis.xdelex(STREAM_KEY_1, id1); + assertThat(results, hasSize(1)); + assertEquals(StreamTrimResult.DELETED, results.get(0)); + + // Verify entry is deleted from stream + assertEquals(1L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void testXdelexWithTrimMode() { + // Add test entries + byte[] id1 = jedis.xadd(STREAM_KEY_1, new XAddParams().id("1-0"), HASH_1); + jedis.xadd(STREAM_KEY_1, new XAddParams().id("2-0"), HASH_2); + + // Test XDELEX with DELETE_REFERENCES mode + List results = jedis.xdelex(STREAM_KEY_1, StreamTrimMode.DELETE_REFERENCES, id1); + assertThat(results, hasSize(1)); + assertEquals(StreamTrimResult.DELETED, results.get(0)); + + // Verify entry is deleted from stream + assertEquals(1L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void testXdelexMultipleEntries() { + // Add test entries + byte[] id1 = jedis.xadd(STREAM_KEY_1, new XAddParams().id("1-0"), HASH_1); + jedis.xadd(STREAM_KEY_1, new XAddParams().id("2-0"), HASH_2); + byte[] id3 = jedis.xadd(STREAM_KEY_1, new XAddParams().id("3-0"), HASH_1); + assertEquals(3L, jedis.xlen(STREAM_KEY_1)); + + // Test XDELEX with multiple IDs + List results = jedis.xdelex(STREAM_KEY_1, id1, id3); + assertThat(results, hasSize(2)); + assertEquals(StreamTrimResult.DELETED, results.get(0)); + assertEquals(StreamTrimResult.DELETED, results.get(1)); + + // Verify two entries are deleted + assertEquals(1L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void testXdelexNonExistentEntries() { + // Add one entry + byte[] id1 = jedis.xadd(STREAM_KEY_1, new XAddParams().id("1-0"), HASH_1); + assertEquals(1L, jedis.xlen(STREAM_KEY_1)); + + // Test XDELEX with mix of existing and non-existent IDs + byte[] nonExistentId = "999-0".getBytes(); + List results = jedis.xdelex(STREAM_KEY_1, id1, nonExistentId); + assertThat(results, hasSize(2)); + assertEquals(StreamTrimResult.DELETED, results.get(0)); // Existing entry + assertEquals(StreamTrimResult.NOT_FOUND, results.get(1)); // Non-existent entry + + // Verify existing entry is deleted + assertEquals(0L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void testXdelexWithConsumerGroups() { + // Add test entries + jedis.xadd(STREAM_KEY_1, new XAddParams().id("1-0"), HASH_1); + jedis.xadd(STREAM_KEY_1, new XAddParams().id("2-0"), HASH_2); + assertEquals(2L, jedis.xlen(STREAM_KEY_1)); + + // Read messages with consumer group to add them to PEL + Map streams = offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY); + List>> messages = jedis.xreadGroupBinary( + GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(2), streams); + + assertEquals(1, messages.size()); + assertEquals(2, messages.get(0).getValue().size()); + + // Acknowledge only the first message + byte[] readId1 = messages.get(0).getValue().get(0).getID().toString().getBytes(); + byte[] readId2 = messages.get(0).getValue().get(1).getID().toString().getBytes(); + jedis.xack(STREAM_KEY_1, GROUP_NAME, readId1); + + // Test XDELEX with ACKNOWLEDGED mode - should only delete acknowledged entries + List results = jedis.xdelex(STREAM_KEY_1, StreamTrimMode.ACKNOWLEDGED, readId1, readId2); + assertThat(results, hasSize(2)); + assertEquals(StreamTrimResult.DELETED, results.get(0)); // id1 was acknowledged + assertEquals(StreamTrimResult.ACKNOWLEDGED_NOT_DELETED, results.get(1)); // id2 not acknowledged + + // Verify only acknowledged entry was deleted + assertEquals(1L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void testXdelexEmptyStream() { + // Test XDELEX on empty stream + byte[] nonExistentId = "1-0".getBytes(); + List results = jedis.xdelex(STREAM_KEY_1, nonExistentId); + assertThat(results, hasSize(1)); + assertEquals(StreamTrimResult.NOT_FOUND, results.get(0)); + } + + // ========== XTRIM Command Tests with trimmingMode ========== + + @Test + public void testXtrimWithKeepReferences() { + // Add test entries + for (int i = 1; i <= 5; i++) { + jedis.xadd(STREAM_KEY_1, new XAddParams().id(i + "-0"), HASH_1); + } + assertEquals(5L, jedis.xlen(STREAM_KEY_1)); + + // Read messages with consumer group to create PEL entries + Map streams = offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY); + jedis.xreadGroupBinary(GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3), streams); + + // Test XTRIM with KEEP_REFERENCES mode - should preserve PEL references + long trimmed = jedis.xtrim(STREAM_KEY_1, XTrimParams.xTrimParams().maxLen(3).trimmingMode(StreamTrimMode.KEEP_REFERENCES)); + assertEquals(2L, trimmed); // Should trim 2 entries + assertEquals(3L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void testXtrimWithAcknowledged() { + // Add test entries + for (int i = 1; i <= 5; i++) { + jedis.xadd(STREAM_KEY_1, new XAddParams().id(i + "-0"), HASH_1); + } + assertEquals(5L, jedis.xlen(STREAM_KEY_1)); + + // Read messages with consumer group + Map streams = offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY); + List>> messages = jedis.xreadGroupBinary( + GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3), streams); + + assertEquals(1, messages.size()); + assertEquals(3, messages.get(0).getValue().size()); + + // Acknowledge only the first 2 messages + byte[] readId1 = messages.get(0).getValue().get(0).getID().toString().getBytes(); + byte[] readId2 = messages.get(0).getValue().get(1).getID().toString().getBytes(); + jedis.xack(STREAM_KEY_1, GROUP_NAME, readId1, readId2); + + // Test XTRIM with ACKNOWLEDGED mode - should only trim acknowledged entries + long trimmed = jedis.xtrim(STREAM_KEY_1, XTrimParams.xTrimParams().maxLen(3).trimmingMode(StreamTrimMode.ACKNOWLEDGED)); + // The exact behavior depends on implementation, but it should respect acknowledgment status + assertTrue(trimmed >= 0); + assertTrue(jedis.xlen(STREAM_KEY_1) <= 5); // Should not exceed original length + } + } diff --git a/src/test/java/redis/clients/jedis/commands/jedis/StreamsCommandsTest.java b/src/test/java/redis/clients/jedis/commands/jedis/StreamsCommandsTest.java index 1a3763b259..54fc4fd772 100644 --- a/src/test/java/redis/clients/jedis/commands/jedis/StreamsCommandsTest.java +++ b/src/test/java/redis/clients/jedis/commands/jedis/StreamsCommandsTest.java @@ -36,6 +36,7 @@ import redis.clients.jedis.exceptions.JedisException; import redis.clients.jedis.params.*; import redis.clients.jedis.resps.*; +import redis.clients.jedis.args.StreamTrimMode; import redis.clients.jedis.util.RedisVersionUtil; import redis.clients.jedis.util.SafeEncoder; @@ -197,6 +198,308 @@ public void xaddParamsId() { assertNotNull(id); } + @Test + public void xaddWithTrimmingModeKeepReferences() { + String streamKey = "xadd-trim-keep-ref-stream"; + String groupName = "test-group"; + String consumerName = "test-consumer"; + Map map = singletonMap("field", "value"); + + // Add initial entries to the stream + for (int i = 1; i <= 5; i++) { + jedis.xadd(streamKey, XAddParams.xAddParams().id(new StreamEntryID(i + "-0")), map); + } + assertEquals(5L, jedis.xlen(streamKey)); + + // Create consumer group and read messages to create PEL entries + jedis.xgroupCreate(streamKey, groupName, new StreamEntryID("0-0"), false); + Map streamQuery = singletonMap(streamKey, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + jedis.xreadGroup(groupName, consumerName, XReadGroupParams.xReadGroupParams().count(3), streamQuery); + + // Verify PEL has entries + List pendingBefore = jedis.xpending(streamKey, groupName, XPendingParams.xPendingParams().count(10)); + assertEquals(3, pendingBefore.size()); + + // Add new entry with maxLen=3 and KEEP_REFERENCES mode + StreamEntryID newId = jedis.xadd(streamKey, XAddParams.xAddParams() + .id(new StreamEntryID("6-0")) + .maxLen(3) + .trimmingMode(StreamTrimMode.KEEP_REFERENCES), map); + assertNotNull(newId); + + // Stream should be trimmed to 3 entries + assertEquals(3L, jedis.xlen(streamKey)); + + // PEL references should be preserved even though entries were trimmed + List pendingAfter = jedis.xpending(streamKey, groupName, XPendingParams.xPendingParams().count(10)); + assertEquals(3, pendingAfter.size()); // PEL entries should still exist + } + + @Test + public void xaddWithTrimmingModeDeleteReferences() { + String streamKey = "xadd-trim-del-ref-stream"; + String groupName = "test-group"; + String consumerName = "test-consumer"; + Map map = singletonMap("field", "value"); + + // Add initial entries to the stream + for (int i = 1; i <= 5; i++) { + jedis.xadd(streamKey, XAddParams.xAddParams().id(new StreamEntryID(i + "-0")), map); + } + assertEquals(5L, jedis.xlen(streamKey)); + + // Create consumer group and read messages to create PEL entries + jedis.xgroupCreate(streamKey, groupName, new StreamEntryID("0-0"), false); + Map streamQuery = singletonMap(streamKey, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + jedis.xreadGroup(groupName, consumerName, XReadGroupParams.xReadGroupParams().count(3), streamQuery); + + // Verify PEL has entries + List pendingBefore = jedis.xpending(streamKey, groupName, XPendingParams.xPendingParams().count(10)); + assertEquals(3, pendingBefore.size()); + + // Add new entry with maxLen=3 and DELETE_REFERENCES mode + StreamEntryID newId = jedis.xadd(streamKey, XAddParams.xAddParams() + .id(new StreamEntryID("6-0")) + .maxLen(3) + .trimmingMode(StreamTrimMode.DELETE_REFERENCES), map); + assertNotNull(newId); + + // Stream should be trimmed to 3 entries + assertEquals(3L, jedis.xlen(streamKey)); + + // PEL references should be removed for trimmed entries + List pendingAfter = jedis.xpending(streamKey, groupName, XPendingParams.xPendingParams().count(10)); + // Only entries that still exist in the stream should remain in PEL + assertTrue(pendingAfter.size() <= 3); + } + + @Test + public void xaddWithTrimmingModeAcknowledged() { + String streamKey = "xadd-trim-acked-stream"; + String groupName = "test-group"; + String consumerName = "test-consumer"; + Map map = singletonMap("field", "value"); + + // Add initial entries to the stream + for (int i = 1; i <= 5; i++) { + jedis.xadd(streamKey, XAddParams.xAddParams().id(new StreamEntryID(i + "-0")), map); + } + assertEquals(5L, jedis.xlen(streamKey)); + + // Create consumer group and read messages + jedis.xgroupCreate(streamKey, groupName, new StreamEntryID("0-0"), false); + Map streamQuery = singletonMap(streamKey, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + List>> messages = jedis.xreadGroup(groupName, consumerName, + XReadGroupParams.xReadGroupParams().count(3), streamQuery); + + // Acknowledge the first 2 messages + StreamEntryID id1 = messages.get(0).getValue().get(0).getID(); + StreamEntryID id2 = messages.get(0).getValue().get(1).getID(); + jedis.xack(streamKey, groupName, id1, id2); + + // Verify PEL state + List pendingBefore = jedis.xpending(streamKey, groupName, XPendingParams.xPendingParams().count(10)); + assertEquals(1, pendingBefore.size()); // Only 1 unacknowledged message + + // Add new entry with maxLen=3 and ACKNOWLEDGED mode + StreamEntryID newId = jedis.xadd(streamKey, XAddParams.xAddParams() + .id(new StreamEntryID("6-0")) + .maxLen(3) + .trimmingMode(StreamTrimMode.ACKNOWLEDGED), map); + assertNotNull(newId); + + // Stream length should respect acknowledgment status + long streamLen = jedis.xlen(streamKey); + assertTrue(streamLen >= 3); // Should not trim unacknowledged entries aggressively + + // PEL should still contain unacknowledged entries + List pendingAfter = jedis.xpending(streamKey, groupName, XPendingParams.xPendingParams().count(10)); + assertFalse(pendingAfter.isEmpty()); // Unacknowledged entries should remain + } + + @Test + public void xaddWithMinIdTrimmingModeKeepReferences() { + String streamKey = "xadd-minid-keep-ref-stream"; + String groupName = "test-group"; + String consumerName = "test-consumer"; + Map map = singletonMap("field", "value"); + + // Add initial entries with specific IDs + for (int i = 1; i <= 5; i++) { + jedis.xadd(streamKey, XAddParams.xAddParams().id(new StreamEntryID("0-" + i)), map); + } + assertEquals(5L, jedis.xlen(streamKey)); + + // Create consumer group and read messages to create PEL entries + jedis.xgroupCreate(streamKey, groupName, new StreamEntryID("0-0"), false); + Map streamQuery = singletonMap(streamKey, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + jedis.xreadGroup(groupName, consumerName, XReadGroupParams.xReadGroupParams().count(3), streamQuery); + + // Verify PEL has entries + List pendingBefore = jedis.xpending(streamKey, groupName, XPendingParams.xPendingParams().count(10)); + assertEquals(3, pendingBefore.size()); + + // Add new entry with minId="0-3" and KEEP_REFERENCES mode (should trim entries < 0-3) + StreamEntryID newId = jedis.xadd(streamKey, XAddParams.xAddParams() + .id(new StreamEntryID("0-6")) + .minId("0-3") + .trimmingMode(StreamTrimMode.KEEP_REFERENCES), map); + assertNotNull(newId); + + // Stream should have entries >= 0-3 plus the new entry + long streamLen = jedis.xlen(streamKey); + assertTrue(streamLen >= 3); // Should keep entries 0-3, 0-4, 0-5, 0-6 + + // PEL references should be preserved even for trimmed entries + List pendingAfter = jedis.xpending(streamKey, groupName, XPendingParams.xPendingParams().count(10)); + assertEquals(3, pendingAfter.size()); // PEL entries should still exist + } + + @Test + public void xaddWithMinIdTrimmingModeDeleteReferences() { + String streamKey = "xadd-minid-del-ref-stream"; + String groupName = "test-group"; + String consumerName = "test-consumer"; + Map map = singletonMap("field", "value"); + + // Add initial entries with specific IDs + for (int i = 1; i <= 5; i++) { + jedis.xadd(streamKey, XAddParams.xAddParams().id(new StreamEntryID("0-" + i)), map); + } + assertEquals(5L, jedis.xlen(streamKey)); + + // Create consumer group and read messages to create PEL entries + jedis.xgroupCreate(streamKey, groupName, new StreamEntryID("0-0"), false); + Map streamQuery = singletonMap(streamKey, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + jedis.xreadGroup(groupName, consumerName, XReadGroupParams.xReadGroupParams().count(3), streamQuery); + + // Verify PEL has entries + List pendingBefore = jedis.xpending(streamKey, groupName, XPendingParams.xPendingParams().count(10)); + assertEquals(3, pendingBefore.size()); + + // Add new entry with minId="0-3" and DELETE_REFERENCES mode + StreamEntryID newId = jedis.xadd(streamKey, XAddParams.xAddParams() + .id(new StreamEntryID("0-6")) + .minId("0-3") + .trimmingMode(StreamTrimMode.DELETE_REFERENCES), map); + assertNotNull(newId); + + // Stream should have entries >= 0-3 plus the new entry + long streamLen = jedis.xlen(streamKey); + assertTrue(streamLen >= 3); + + // PEL references should be removed for trimmed entries (0-1, 0-2) + List pendingAfter = jedis.xpending(streamKey, groupName, XPendingParams.xPendingParams().count(10)); + // Only entries that still exist in the stream should remain in PEL + assertTrue(pendingAfter.size() <= pendingBefore.size()); + } + + @Test + public void xaddWithApproximateTrimmingAndTrimmingMode() { + String streamKey = "xadd-approx-trim-stream"; + String groupName = "test-group"; + String consumerName = "test-consumer"; + Map map = singletonMap("field", "value"); + + // Add initial entries + for (int i = 1; i <= 10; i++) { + jedis.xadd(streamKey, XAddParams.xAddParams().id(new StreamEntryID(i + "-0")), map); + } + assertEquals(10L, jedis.xlen(streamKey)); + + // Create consumer group and read messages + jedis.xgroupCreate(streamKey, groupName, new StreamEntryID("0-0"), false); + Map streamQuery = singletonMap(streamKey, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + jedis.xreadGroup(groupName, consumerName, XReadGroupParams.xReadGroupParams().count(5), streamQuery); + + // Add new entry with approximate trimming and KEEP_REFERENCES mode + StreamEntryID newId = jedis.xadd(streamKey, XAddParams.xAddParams() + .id(new StreamEntryID("11-0")) + .maxLen(5) + .approximateTrimming() + .trimmingMode(StreamTrimMode.KEEP_REFERENCES), map); + assertNotNull(newId); + + // With approximate trimming, the exact length may vary but should be around the target + long streamLen = jedis.xlen(streamKey); + assertTrue(streamLen >= 5); // Should be approximately 5, but may be more due to approximation + + // PEL should preserve references + List pendingAfter = jedis.xpending(streamKey, groupName, XPendingParams.xPendingParams().count(10)); + assertEquals(5, pendingAfter.size()); // All read messages should remain in PEL + } + + @Test + public void xaddWithExactTrimmingAndTrimmingMode() { + String streamKey = "xadd-exact-trim-mode-stream"; + String groupName = "test-group"; + String consumerName = "test-consumer"; + Map map = singletonMap("field", "value"); + + // Add initial entries + for (int i = 1; i <= 5; i++) { + jedis.xadd(streamKey, XAddParams.xAddParams().id(new StreamEntryID(i + "-0")), map); + } + assertEquals(5L, jedis.xlen(streamKey)); + + // Create consumer group and read messages + jedis.xgroupCreate(streamKey, groupName, new StreamEntryID("0-0"), false); + Map streamQuery = singletonMap(streamKey, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + jedis.xreadGroup(groupName, consumerName, XReadGroupParams.xReadGroupParams().count(3), streamQuery); + + // Add new entry with exact trimming and DELETE_REFERENCES mode + StreamEntryID newId = jedis.xadd(streamKey, XAddParams.xAddParams() + .id(new StreamEntryID("6-0")) + .maxLen(3) + .exactTrimming() + .trimmingMode(StreamTrimMode.DELETE_REFERENCES), map); + assertNotNull(newId); + + // With exact trimming, stream should be exactly 3 entries + assertEquals(3L, jedis.xlen(streamKey)); + + // PEL references should be cleaned up for trimmed entries + List pendingAfter = jedis.xpending(streamKey, groupName, XPendingParams.xPendingParams().count(10)); + // Only entries that still exist in the stream should remain in PEL + assertTrue(pendingAfter.size() <= 3); + } + + @Test + public void xaddWithLimitAndTrimmingMode() { + String streamKey = "xadd-limit-trim-mode-stream"; + String groupName = "test-group"; + String consumerName = "test-consumer"; + Map map = singletonMap("field", "value"); + + // Add initial entries + for (int i = 1; i <= 10; i++) { + jedis.xadd(streamKey, XAddParams.xAddParams().id(new StreamEntryID(i + "-0")), map); + } + assertEquals(10L, jedis.xlen(streamKey)); + + // Create consumer group and read messages + jedis.xgroupCreate(streamKey, groupName, new StreamEntryID("0-0"), false); + Map streamQuery = singletonMap(streamKey, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + jedis.xreadGroup(groupName, consumerName, XReadGroupParams.xReadGroupParams().count(5), streamQuery); + + // Add new entry with limit and KEEP_REFERENCES mode (limit requires approximate trimming) + StreamEntryID newId = jedis.xadd(streamKey, XAddParams.xAddParams() + .id(new StreamEntryID("11-0")) + .maxLen(5) + .approximateTrimming() // Required for limit to work + .limit(2) // Limit the number of entries to examine for trimming + .trimmingMode(StreamTrimMode.KEEP_REFERENCES), map); + assertNotNull(newId); + + // With limit, trimming may be less aggressive + long streamLen = jedis.xlen(streamKey); + assertTrue(streamLen >= 5); // Should be at least 5, but may be more due to limit + + // PEL should preserve references + List pendingAfter = jedis.xpending(streamKey, groupName, XPendingParams.xPendingParams().count(10)); + assertEquals(5, pendingAfter.size()); // All read messages should remain in PEL + } + @Test public void xdel() { Map map1 = new HashMap<>(); @@ -228,7 +531,7 @@ public void xlen() { @Test public void xrange() { - List range = jedis.xrange("xrange-stream", (StreamEntryID) null, + List range = jedis.xrange("xrange-stream", null, (StreamEntryID) null, Integer.MAX_VALUE); assertEquals(0, range.size()); @@ -236,7 +539,7 @@ public void xrange() { map.put("f1", "v1"); StreamEntryID id1 = jedis.xadd("xrange-stream", (StreamEntryID) null, map); StreamEntryID id2 = jedis.xadd("xrange-stream", (StreamEntryID) null, map); - List range2 = jedis.xrange("xrange-stream", (StreamEntryID) null, + List range2 = jedis.xrange("xrange-stream", null, (StreamEntryID) null, 3); assertEquals(2, range2.size()); assertEquals(range2.get(0).toString(), id1 + " " + map); @@ -257,7 +560,7 @@ public void xrange() { List range7 = jedis.xrange("xrange-stream", id3, id3, 4); assertEquals(1, range7.size()); - List range8 = jedis.xrange("xrange-stream", (StreamEntryID) null, (StreamEntryID) null); + List range8 = jedis.xrange("xrange-stream", null, (StreamEntryID) null); assertEquals(3, range8.size()); range8 = jedis.xrange("xrange-stream", StreamEntryID.MINIMUM_ID, StreamEntryID.MAXIMUM_ID); assertEquals(3, range8.size()); @@ -292,7 +595,7 @@ public void xreadWithParams() { Map map = new HashMap<>(); map.put("f1", "v1"); StreamEntryID id1 = jedis.xadd(key1, (StreamEntryID) null, map); - StreamEntryID id2 = jedis.xadd(key2, (StreamEntryID) null, map); + jedis.xadd(key2, (StreamEntryID) null, map); // Read only a single Stream List>> streams1 = jedis.xread(XReadParams.xReadParams().count(1).block(1), streamQeury1); diff --git a/src/test/java/redis/clients/jedis/commands/unified/StreamsBinaryCommandsTestBase.java b/src/test/java/redis/clients/jedis/commands/unified/StreamsBinaryCommandsTestBase.java index 816dd8e6ac..abf30326cd 100644 --- a/src/test/java/redis/clients/jedis/commands/unified/StreamsBinaryCommandsTestBase.java +++ b/src/test/java/redis/clients/jedis/commands/unified/StreamsBinaryCommandsTestBase.java @@ -4,11 +4,14 @@ import org.junit.jupiter.api.Test; import redis.clients.jedis.RedisProtocol; import redis.clients.jedis.StreamEntryID; +import redis.clients.jedis.args.StreamTrimMode; import redis.clients.jedis.exceptions.JedisDataException; import redis.clients.jedis.params.XAddParams; import redis.clients.jedis.params.XReadGroupParams; import redis.clients.jedis.params.XReadParams; +import redis.clients.jedis.params.XTrimParams; import redis.clients.jedis.resps.StreamEntryBinary; +import redis.clients.jedis.resps.StreamTrimResult; import java.util.ArrayList; import java.util.HashMap; @@ -19,7 +22,10 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasSize; import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static redis.clients.jedis.StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY; import static redis.clients.jedis.util.StreamEntryBinaryListMatcher.equalsStreamEntries; @@ -252,4 +258,279 @@ public void xreadGroupBinaryAsMapMultipleStreams() { assertThat(actualEntries.get(STREAM_KEY_2), equalsStreamEntries(stream2Entries)); } + // ========== XACKDEL Command Tests ========== + + @Test + public void testXackdel() { + setUpTestStream(); + + // Add a message to the stream + byte[] messageId = jedis.xadd(STREAM_KEY_1, new XAddParams().id("1-0"), HASH_1); + assertNotNull(messageId); + assertEquals(1L, jedis.xlen(STREAM_KEY_1)); + + // Read the message with consumer group to add it to PEL + Map streams = offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY); + List>> messages = jedis.xreadGroupBinary( + GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(1), streams); + + assertEquals(1, messages.size()); + assertEquals(1, messages.get(0).getValue().size()); + byte[] readMessageId = messages.get(0).getValue().get(0).getID().toString().getBytes(); + + // Test XACKDEL - should acknowledge and delete the message + List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, readMessageId); + assertThat(results, hasSize(1)); + assertEquals(StreamTrimResult.DELETED, results.get(0)); + + // Verify message is deleted from stream + assertEquals(0L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void testXackdelWithTrimMode() { + setUpTestStream(); + + // Add multiple messages + jedis.xadd(STREAM_KEY_1, new XAddParams().id("1-0"), HASH_1); + jedis.xadd(STREAM_KEY_1, new XAddParams().id("2-0"), HASH_2); + assertEquals(2L, jedis.xlen(STREAM_KEY_1)); + + // Read the messages with consumer group + Map streams = offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY); + List>> messages = jedis.xreadGroupBinary( + GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(2), streams); + + assertEquals(1, messages.size()); + assertEquals(2, messages.get(0).getValue().size()); + + // Test XACKDEL with KEEP_REFERENCES mode + byte[] readId1 = messages.get(0).getValue().get(0).getID().toString().getBytes(); + List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, StreamTrimMode.KEEP_REFERENCES, readId1); + assertThat(results, hasSize(1)); + assertEquals(StreamTrimResult.DELETED, results.get(0)); + + // Verify one message is deleted + assertEquals(1L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void testXackdelUnreadMessages() { + setUpTestStream(); + + // Add test entries but don't read them + byte[] id1 = jedis.xadd(STREAM_KEY_1, new XAddParams().id("1-0"), HASH_1); + + // Test XACKDEL on unread messages - should return NOT_FOUND for PEL + List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, id1); + + assertThat(results, hasSize(1)); + // Should return NOT_FOUND because message was never read by the consumer group + assertEquals(StreamTrimResult.NOT_FOUND, results.get(0)); + + // Stream should still contain the message + assertEquals(1L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void testXackdelMultipleMessages() { + setUpTestStream(); + + // Add multiple messages + jedis.xadd(STREAM_KEY_1, new XAddParams().id("1-0"), HASH_1); + jedis.xadd(STREAM_KEY_1, new XAddParams().id("2-0"), HASH_2); + jedis.xadd(STREAM_KEY_1, new XAddParams().id("3-0"), HASH_1); + assertEquals(3L, jedis.xlen(STREAM_KEY_1)); + + // Read the messages with consumer group + Map streams = offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY); + List>> messages = jedis.xreadGroupBinary( + GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3), streams); + + assertEquals(1, messages.size()); + assertEquals(3, messages.get(0).getValue().size()); + + // Test XACKDEL with multiple IDs + byte[] readId1 = messages.get(0).getValue().get(0).getID().toString().getBytes(); + byte[] readId2 = messages.get(0).getValue().get(1).getID().toString().getBytes(); + List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, readId1, readId2); + assertThat(results, hasSize(2)); + assertEquals(StreamTrimResult.DELETED, results.get(0)); + assertEquals(StreamTrimResult.DELETED, results.get(1)); + + // Verify two messages are deleted + assertEquals(1L, jedis.xlen(STREAM_KEY_1)); + } + + // ========== XDELEX Command Tests ========== + + @Test + public void testXdelex() { + setUpTestStream(); + + // Add test entries + byte[] id1 = jedis.xadd(STREAM_KEY_1, new XAddParams().id("1-0"), HASH_1); + jedis.xadd(STREAM_KEY_1, new XAddParams().id("2-0"), HASH_2); + assertEquals(2L, jedis.xlen(STREAM_KEY_1)); + + // Test basic XDELEX without parameters (should behave like XDEL with KEEP_REFERENCES) + List results = jedis.xdelex(STREAM_KEY_1, id1); + assertThat(results, hasSize(1)); + assertEquals(StreamTrimResult.DELETED, results.get(0)); + + // Verify entry is deleted from stream + assertEquals(1L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void testXdelexWithTrimMode() { + setUpTestStream(); + + // Add test entries + byte[] id1 = jedis.xadd(STREAM_KEY_1, new XAddParams().id("1-0"), HASH_1); + jedis.xadd(STREAM_KEY_1, new XAddParams().id("2-0"), HASH_2); + + // Test XDELEX with DELETE_REFERENCES mode + List results = jedis.xdelex(STREAM_KEY_1, StreamTrimMode.DELETE_REFERENCES, id1); + assertThat(results, hasSize(1)); + assertEquals(StreamTrimResult.DELETED, results.get(0)); + + // Verify entry is deleted from stream + assertEquals(1L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void testXdelexMultipleEntries() { + setUpTestStream(); + + // Add test entries + byte[] id1 = jedis.xadd(STREAM_KEY_1, new XAddParams().id("1-0"), HASH_1); + jedis.xadd(STREAM_KEY_1, new XAddParams().id("2-0"), HASH_2); + byte[] id3 = jedis.xadd(STREAM_KEY_1, new XAddParams().id("3-0"), HASH_1); + assertEquals(3L, jedis.xlen(STREAM_KEY_1)); + + // Test XDELEX with multiple IDs + List results = jedis.xdelex(STREAM_KEY_1, id1, id3); + assertThat(results, hasSize(2)); + assertEquals(StreamTrimResult.DELETED, results.get(0)); + assertEquals(StreamTrimResult.DELETED, results.get(1)); + + // Verify two entries are deleted + assertEquals(1L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void testXdelexNonExistentEntries() { + setUpTestStream(); + + // Add one entry + byte[] id1 = jedis.xadd(STREAM_KEY_1, new XAddParams().id("1-0"), HASH_1); + assertEquals(1L, jedis.xlen(STREAM_KEY_1)); + + // Test XDELEX with mix of existing and non-existent IDs + byte[] nonExistentId = "999-0".getBytes(); + List results = jedis.xdelex(STREAM_KEY_1, id1, nonExistentId); + assertThat(results, hasSize(2)); + assertEquals(StreamTrimResult.DELETED, results.get(0)); // Existing entry + assertEquals(StreamTrimResult.NOT_FOUND, results.get(1)); // Non-existent entry + + // Verify existing entry is deleted + assertEquals(0L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void testXdelexWithConsumerGroups() { + setUpTestStream(); + + // Add test entries + jedis.xadd(STREAM_KEY_1, new XAddParams().id("1-0"), HASH_1); + jedis.xadd(STREAM_KEY_1, new XAddParams().id("2-0"), HASH_2); + assertEquals(2L, jedis.xlen(STREAM_KEY_1)); + + // Read messages with consumer group to add them to PEL + Map streams = offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY); + List>> messages = jedis.xreadGroupBinary( + GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(2), streams); + + assertEquals(1, messages.size()); + assertEquals(2, messages.get(0).getValue().size()); + + // Acknowledge only the first message + byte[] readId1 = messages.get(0).getValue().get(0).getID().toString().getBytes(); + byte[] readId2 = messages.get(0).getValue().get(1).getID().toString().getBytes(); + jedis.xack(STREAM_KEY_1, GROUP_NAME, readId1); + + // Test XDELEX with ACKNOWLEDGED mode - should only delete acknowledged entries + List results = jedis.xdelex(STREAM_KEY_1, StreamTrimMode.ACKNOWLEDGED, readId1, readId2); + assertThat(results, hasSize(2)); + assertEquals(StreamTrimResult.DELETED, results.get(0)); // id1 was acknowledged + assertEquals(StreamTrimResult.ACKNOWLEDGED_NOT_DELETED, results.get(1)); // id2 not acknowledged + + // Verify only acknowledged entry was deleted + assertEquals(1L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void testXdelexEmptyStream() { + setUpTestStream(); + + // Test XDELEX on empty stream + byte[] nonExistentId = "1-0".getBytes(); + List results = jedis.xdelex(STREAM_KEY_1, nonExistentId); + assertThat(results, hasSize(1)); + assertEquals(StreamTrimResult.NOT_FOUND, results.get(0)); + } + + // ========== XTRIM Command Tests with trimmingMode ========== + + @Test + public void testXtrimWithKeepReferences() { + setUpTestStream(); + + // Add test entries + for (int i = 1; i <= 5; i++) { + jedis.xadd(STREAM_KEY_1, new XAddParams().id(i + "-0"), HASH_1); + } + assertEquals(5L, jedis.xlen(STREAM_KEY_1)); + + // Read messages with consumer group to create PEL entries + Map streams = offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY); + jedis.xreadGroupBinary(GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3), streams); + + // Test XTRIM with KEEP_REFERENCES mode - should preserve PEL references + long trimmed = jedis.xtrim(STREAM_KEY_1, XTrimParams.xTrimParams().maxLen(3).trimmingMode(StreamTrimMode.KEEP_REFERENCES)); + assertEquals(2L, trimmed); // Should trim 2 entries + assertEquals(3L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void testXtrimWithAcknowledged() { + setUpTestStream(); + + // Add test entries + for (int i = 1; i <= 5; i++) { + jedis.xadd(STREAM_KEY_1, new XAddParams().id(i + "-0"), HASH_1); + } + assertEquals(5L, jedis.xlen(STREAM_KEY_1)); + + // Read messages with consumer group + Map streams = offsets(STREAM_KEY_1, XREADGROUP_UNDELIVERED_ENTRY); + List>> messages = jedis.xreadGroupBinary( + GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3), streams); + + assertEquals(1, messages.size()); + assertEquals(3, messages.get(0).getValue().size()); + + // Acknowledge only the first 2 messages + byte[] readId1 = messages.get(0).getValue().get(0).getID().toString().getBytes(); + byte[] readId2 = messages.get(0).getValue().get(1).getID().toString().getBytes(); + jedis.xack(STREAM_KEY_1, GROUP_NAME, readId1, readId2); + + // Test XTRIM with ACKNOWLEDGED mode - should only trim acknowledged entries + long trimmed = jedis.xtrim(STREAM_KEY_1, XTrimParams.xTrimParams().maxLen(3).trimmingMode(StreamTrimMode.ACKNOWLEDGED)); + // The exact behavior depends on implementation, but it should respect acknowledgment status + assertTrue(trimmed >= 0); + assertTrue(jedis.xlen(STREAM_KEY_1) <= 5); // Should not exceed original length + } + } diff --git a/src/test/java/redis/clients/jedis/commands/unified/StreamsCommandsTestBase.java b/src/test/java/redis/clients/jedis/commands/unified/StreamsCommandsTestBase.java new file mode 100644 index 0000000000..e1cefa4093 --- /dev/null +++ b/src/test/java/redis/clients/jedis/commands/unified/StreamsCommandsTestBase.java @@ -0,0 +1,846 @@ +package redis.clients.jedis.commands.unified; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import redis.clients.jedis.RedisProtocol; +import redis.clients.jedis.StreamEntryID; +import redis.clients.jedis.args.StreamTrimMode; +import redis.clients.jedis.exceptions.JedisDataException; +import redis.clients.jedis.params.XAddParams; +import redis.clients.jedis.params.XPendingParams; +import redis.clients.jedis.params.XReadGroupParams; +import redis.clients.jedis.params.XTrimParams; +import redis.clients.jedis.resps.StreamEntry; +import redis.clients.jedis.resps.StreamPendingEntry; +import redis.clients.jedis.resps.StreamTrimResult; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static java.util.Collections.singletonMap; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +public abstract class StreamsCommandsTestBase extends UnifiedJedisCommandsTestBase { + + protected static final String STREAM_KEY_1 = "{stream}-1"; + protected static final String STREAM_KEY_2 = "{stream}-2"; + protected static final String GROUP_NAME = "group-1"; + protected static final String CONSUMER_NAME = "consumer-1"; + + protected static final String FIELD_KEY_1 = "field-1"; + protected static final String VALUE_1 = "value-1"; + protected static final String FIELD_KEY_2 = "field-2"; + protected static final String VALUE_2 = "value-2"; + protected static final Map HASH_1 = singletonMap(FIELD_KEY_1, VALUE_1); + protected static final Map HASH_2 = singletonMap(FIELD_KEY_2, VALUE_2); + + public StreamsCommandsTestBase(RedisProtocol protocol) { + super(protocol); + } + + /** + * Creates a map of stream keys to StreamEntryID objects. + * @param streamOffsets Array of stream key and offset pairs + * @return Map of stream keys to StreamEntryID objects + */ + public static Map offsets(Object... streamOffsets) { + if (streamOffsets.length % 2 != 0) { + throw new IllegalArgumentException("Stream offsets must be provided as key-value pairs"); + } + + Map result = new HashMap<>(); + for (int i = 0; i < streamOffsets.length; i += 2) { + String key = (String) streamOffsets[i]; + Object value = streamOffsets[i + 1]; + + StreamEntryID id; + if (value instanceof String) { + id = new StreamEntryID((String) value); + } else if (value instanceof StreamEntryID) { + id = (StreamEntryID) value; + } else { + throw new IllegalArgumentException("Offset must be a String or StreamEntryID"); + } + + result.put(key, id); + } + + return result; + } + + @BeforeEach + public void setUp() { + setUpTestClient(); + setUpTestStream(); + } + + protected void setUpTestClient() { + } + + public void setUpTestStream() { + jedis.del(STREAM_KEY_1); + jedis.del(STREAM_KEY_2); + try { + jedis.xgroupCreate(STREAM_KEY_1, GROUP_NAME, StreamEntryID.XGROUP_LAST_ENTRY, true); + } catch (JedisDataException e) { + if (!e.getMessage().contains("BUSYGROUP")) { + throw e; + } + } + try { + jedis.xgroupCreate(STREAM_KEY_2, GROUP_NAME, StreamEntryID.XGROUP_LAST_ENTRY, true); + } catch (JedisDataException e) { + if (!e.getMessage().contains("BUSYGROUP")) { + throw e; + } + } + } + + // ========== XADD Command Tests ========== + + @Test + public void xaddBasic() { + setUpTestStream(); + + // Test basic XADD with auto-generated ID + StreamEntryID id1 = jedis.xadd(STREAM_KEY_1, StreamEntryID.NEW_ENTRY, HASH_1); + assertNotNull(id1); + assertEquals(1L, jedis.xlen(STREAM_KEY_1)); + + // Test XADD with multiple fields + Map multiFieldHash = new HashMap<>(); + multiFieldHash.put("field1", "value1"); + multiFieldHash.put("field2", "value2"); + multiFieldHash.put("field3", "value3"); + + StreamEntryID id2 = jedis.xadd(STREAM_KEY_1, StreamEntryID.NEW_ENTRY, multiFieldHash); + assertNotNull(id2); + assertTrue(id2.compareTo(id1) > 0); + assertEquals(2L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void xaddWithSpecificId() { + setUpTestStream(); + + // Test XADD with specific ID + StreamEntryID specificId = new StreamEntryID("1000-0"); + StreamEntryID resultId = jedis.xadd(STREAM_KEY_1, specificId, HASH_1); + assertEquals(specificId, resultId); + assertEquals(1L, jedis.xlen(STREAM_KEY_1)); + + // Test XADD with ID that must be greater than previous + StreamEntryID nextId = new StreamEntryID("1001-0"); + StreamEntryID resultId2 = jedis.xadd(STREAM_KEY_1, nextId, HASH_2); + assertEquals(nextId, resultId2); + assertEquals(2L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void xaddWithParams() { + setUpTestStream(); + + // Test XADD with maxLen parameter + for (int i = 1; i <= 5; i++) { + jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().id(new StreamEntryID(i + "-0")), HASH_1); + } + assertEquals(5L, jedis.xlen(STREAM_KEY_1)); + + // Add with maxLen=3, should trim to 3 entries + StreamEntryID id6 = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().id(new StreamEntryID("6-0")).maxLen(3), HASH_2); + assertNotNull(id6); + assertEquals(3L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void xaddErrorCases() { + setUpTestStream(); + + // Test XADD with empty hash should fail + try { + Map emptyHash = new HashMap<>(); + jedis.xadd(STREAM_KEY_1, StreamEntryID.NEW_ENTRY, emptyHash); + fail("Should throw JedisDataException for empty hash"); + } catch (JedisDataException expected) { + assertTrue(expected.getMessage().contains("wrong number of arguments")); + } + + // Test XADD with noMkStream on non-existent stream + StreamEntryID result = jedis.xadd("non-existent-stream", XAddParams.xAddParams().noMkStream(), HASH_1); + assertNull(result); + } + + @Test + public void xaddWithTrimmingModeKeepReferences() { + setUpTestStream(); + Map map = singletonMap("field", "value"); + + // Add initial entries to the stream + for (int i = 1; i <= 5; i++) { + jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().id(new StreamEntryID(i + "-0")), map); + } + assertEquals(5L, jedis.xlen(STREAM_KEY_1)); + + // Create consumer group and read messages to create PEL entries + Map streamQuery = singletonMap(STREAM_KEY_1, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3), streamQuery); + + // Verify PEL has entries + List pendingBefore = jedis.xpending(STREAM_KEY_1, GROUP_NAME, XPendingParams.xPendingParams().count(10)); + assertEquals(3, pendingBefore.size()); + + // Add new entry with maxLen=3 and KEEP_REFERENCES mode + StreamEntryID newId = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams() + .id(new StreamEntryID("6-0")) + .maxLen(3) + .trimmingMode(StreamTrimMode.KEEP_REFERENCES), map); + assertNotNull(newId); + + // Stream should be trimmed to 3 entries + assertEquals(3L, jedis.xlen(STREAM_KEY_1)); + + // PEL references should be preserved even though entries were trimmed + List pendingAfter = jedis.xpending(STREAM_KEY_1, GROUP_NAME, XPendingParams.xPendingParams().count(10)); + assertEquals(3, pendingAfter.size()); // PEL entries should still exist + } + + @Test + public void xaddWithTrimmingModeDeleteReferences() { + setUpTestStream(); + Map map = singletonMap("field", "value"); + + // Add initial entries to the stream + for (int i = 1; i <= 5; i++) { + jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().id(new StreamEntryID(i + "-0")), map); + } + assertEquals(5L, jedis.xlen(STREAM_KEY_1)); + + // Create consumer group and read messages to create PEL entries + Map streamQuery = singletonMap(STREAM_KEY_1, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3), streamQuery); + + // Verify PEL has entries + List pendingBefore = jedis.xpending(STREAM_KEY_1, GROUP_NAME, XPendingParams.xPendingParams().count(10)); + assertEquals(3, pendingBefore.size()); + + // Add new entry with maxLen=3 and DELETE_REFERENCES mode + StreamEntryID newId = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams() + .id(new StreamEntryID("6-0")) + .maxLen(3) + .trimmingMode(StreamTrimMode.DELETE_REFERENCES), map); + assertNotNull(newId); + + // Stream should be trimmed to 3 entries + assertEquals(3L, jedis.xlen(STREAM_KEY_1)); + + // PEL references should be removed for trimmed entries + List pendingAfter = jedis.xpending(STREAM_KEY_1, GROUP_NAME, XPendingParams.xPendingParams().count(10)); + // Only entries that still exist in the stream should remain in PEL + assertTrue(pendingAfter.size() <= 3); + } + + @Test + public void xaddWithTrimmingModeAcknowledged() { + setUpTestStream(); + Map map = singletonMap("field", "value"); + + // Add initial entries to the stream + for (int i = 1; i <= 5; i++) { + jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().id(new StreamEntryID(i + "-0")), map); + } + assertEquals(5L, jedis.xlen(STREAM_KEY_1)); + + // Create consumer group and read messages + Map streamQuery = singletonMap(STREAM_KEY_1, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + List>> messages = jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, + XReadGroupParams.xReadGroupParams().count(3), streamQuery); + + // Acknowledge the first 2 messages + StreamEntryID id1 = messages.get(0).getValue().get(0).getID(); + StreamEntryID id2 = messages.get(0).getValue().get(1).getID(); + jedis.xack(STREAM_KEY_1, GROUP_NAME, id1, id2); + + // Verify PEL state + List pendingBefore = jedis.xpending(STREAM_KEY_1, GROUP_NAME, XPendingParams.xPendingParams().count(10)); + assertEquals(1, pendingBefore.size()); // Only 1 unacknowledged message + + // Add new entry with maxLen=3 and ACKNOWLEDGED mode + StreamEntryID newId = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams() + .id(new StreamEntryID("6-0")) + .maxLen(3) + .trimmingMode(StreamTrimMode.ACKNOWLEDGED), map); + assertNotNull(newId); + + // Stream length should respect acknowledgment status + long streamLen = jedis.xlen(STREAM_KEY_1); + assertTrue(streamLen >= 3); // Should not trim unacknowledged entries aggressively + + // PEL should still contain unacknowledged entries + List pendingAfter = jedis.xpending(STREAM_KEY_1, GROUP_NAME, XPendingParams.xPendingParams().count(10)); + assertTrue(pendingAfter.size() >= 1); // Unacknowledged entries should remain + } + + // ========== XTRIM Command Tests ========== + + @Test + public void xtrimBasic() { + setUpTestStream(); + + // Add test entries + for (int i = 1; i <= 5; i++) { + jedis.xadd(STREAM_KEY_1, new StreamEntryID(i + "-0"), HASH_1); + } + assertEquals(5L, jedis.xlen(STREAM_KEY_1)); + + // Test basic XTRIM with maxLen + long trimmed = jedis.xtrim(STREAM_KEY_1, 3, false); + assertEquals(2L, trimmed); // Should trim 2 entries + assertEquals(3L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void xtrimWithParams() { + setUpTestStream(); + + // Add test entries with specific IDs + for (int i = 1; i <= 5; i++) { + jedis.xadd(STREAM_KEY_1, new StreamEntryID("0-" + i), HASH_1); + } + assertEquals(5L, jedis.xlen(STREAM_KEY_1)); + + // Test XTRIM with XTrimParams and exact trimming + long trimmed = jedis.xtrim(STREAM_KEY_1, XTrimParams.xTrimParams().maxLen(3).exactTrimming()); + assertEquals(2L, trimmed); + assertEquals(3L, jedis.xlen(STREAM_KEY_1)); + + // Test XTRIM with minId + long trimmed2 = jedis.xtrim(STREAM_KEY_1, XTrimParams.xTrimParams().minId("0-4").exactTrimming()); + assertEquals(1L, trimmed2); // Should trim entries with ID < 0-4 + assertEquals(2L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void xtrimApproximate() { + setUpTestStream(); + + // Add many entries + for (int i = 1; i <= 10; i++) { + jedis.xadd(STREAM_KEY_1, new StreamEntryID(i + "-0"), HASH_1); + } + assertEquals(10L, jedis.xlen(STREAM_KEY_1)); + + // Test approximate trimming + long trimmed = jedis.xtrim(STREAM_KEY_1, 5, true); + assertTrue(trimmed >= 0); // Approximate trimming may trim different amounts + assertTrue(jedis.xlen(STREAM_KEY_1) <= 10); // Should not exceed original length + } + + @Test + public void xaddWithMinIdTrimmingModeKeepReferences() { + setUpTestStream(); + Map map = singletonMap("field", "value"); + + // Add initial entries with specific IDs + for (int i = 1; i <= 5; i++) { + jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().id(new StreamEntryID("0-" + i)), map); + } + assertEquals(5L, jedis.xlen(STREAM_KEY_1)); + + // Create consumer group and read messages to create PEL entries + Map streamQuery = singletonMap(STREAM_KEY_1, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3), streamQuery); + + // Verify PEL has entries + List pendingBefore = jedis.xpending(STREAM_KEY_1, GROUP_NAME, XPendingParams.xPendingParams().count(10)); + assertEquals(3, pendingBefore.size()); + + // Add new entry with minId="0-3" and KEEP_REFERENCES mode (should trim entries < 0-3) + StreamEntryID newId = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams() + .id(new StreamEntryID("0-6")) + .minId("0-3") + .trimmingMode(StreamTrimMode.KEEP_REFERENCES), map); + assertNotNull(newId); + + // Stream should have entries >= 0-3 plus the new entry + long streamLen = jedis.xlen(STREAM_KEY_1); + assertTrue(streamLen >= 3); // Should keep entries 0-3, 0-4, 0-5, 0-6 + + // PEL references should be preserved even for trimmed entries + List pendingAfter = jedis.xpending(STREAM_KEY_1, GROUP_NAME, XPendingParams.xPendingParams().count(10)); + assertEquals(3, pendingAfter.size()); // PEL entries should still exist + } + + @Test + public void xaddWithMinIdTrimmingModeDeleteReferences() { + setUpTestStream(); + Map map = singletonMap("field", "value"); + + // Add initial entries with specific IDs + for (int i = 1; i <= 5; i++) { + jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().id(new StreamEntryID("0-" + i)), map); + } + assertEquals(5L, jedis.xlen(STREAM_KEY_1)); + + // Create consumer group and read messages to create PEL entries + Map streamQuery = singletonMap(STREAM_KEY_1, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3), streamQuery); + + // Verify PEL has entries + List pendingBefore = jedis.xpending(STREAM_KEY_1, GROUP_NAME, XPendingParams.xPendingParams().count(10)); + assertEquals(3, pendingBefore.size()); + + // Add new entry with minId="0-3" and DELETE_REFERENCES mode + StreamEntryID newId = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams() + .id(new StreamEntryID("0-6")) + .minId("0-3") + .trimmingMode(StreamTrimMode.DELETE_REFERENCES), map); + assertNotNull(newId); + + // Stream should have entries >= 0-3 plus the new entry + long streamLen = jedis.xlen(STREAM_KEY_1); + assertTrue(streamLen >= 3); + + // PEL references should be removed for trimmed entries (0-1, 0-2) + List pendingAfter = jedis.xpending(STREAM_KEY_1, GROUP_NAME, XPendingParams.xPendingParams().count(10)); + // Only entries that still exist in the stream should remain in PEL + assertTrue(pendingAfter.size() <= pendingBefore.size()); + } + + @Test + public void xaddWithApproximateTrimmingAndTrimmingMode() { + setUpTestStream(); + Map map = singletonMap("field", "value"); + + // Add initial entries + for (int i = 1; i <= 10; i++) { + jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().id(new StreamEntryID(i + "-0")), map); + } + assertEquals(10L, jedis.xlen(STREAM_KEY_1)); + + // Create consumer group and read messages + Map streamQuery = singletonMap(STREAM_KEY_1, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(5), streamQuery); + + // Add new entry with approximate trimming and KEEP_REFERENCES mode + StreamEntryID newId = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams() + .id(new StreamEntryID("11-0")) + .maxLen(5) + .approximateTrimming() + .trimmingMode(StreamTrimMode.KEEP_REFERENCES), map); + assertNotNull(newId); + + // With approximate trimming, the exact length may vary but should be around the target + long streamLen = jedis.xlen(STREAM_KEY_1); + assertTrue(streamLen >= 5); // Should be approximately 5, but may be more due to approximation + + // PEL should preserve references + List pendingAfter = jedis.xpending(STREAM_KEY_1, GROUP_NAME, XPendingParams.xPendingParams().count(10)); + assertEquals(5, pendingAfter.size()); // All read messages should remain in PEL + } + + @Test + public void xaddWithExactTrimmingAndTrimmingMode() { + setUpTestStream(); + Map map = singletonMap("field", "value"); + + // Add initial entries + for (int i = 1; i <= 5; i++) { + jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().id(new StreamEntryID(i + "-0")), map); + } + assertEquals(5L, jedis.xlen(STREAM_KEY_1)); + + // Create consumer group and read messages + Map streamQuery = singletonMap(STREAM_KEY_1, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3), streamQuery); + + // Add new entry with exact trimming and DELETE_REFERENCES mode + StreamEntryID newId = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams() + .id(new StreamEntryID("6-0")) + .maxLen(3) + .exactTrimming() + .trimmingMode(StreamTrimMode.DELETE_REFERENCES), map); + assertNotNull(newId); + + // With exact trimming, stream should be exactly 3 entries + assertEquals(3L, jedis.xlen(STREAM_KEY_1)); + + // PEL references should be cleaned up for trimmed entries + List pendingAfter = jedis.xpending(STREAM_KEY_1, GROUP_NAME, XPendingParams.xPendingParams().count(10)); + // Only entries that still exist in the stream should remain in PEL + assertTrue(pendingAfter.size() <= 3); + } + + @Test + public void xaddWithLimitAndTrimmingMode() { + setUpTestStream(); + Map map = singletonMap("field", "value"); + + // Add initial entries + for (int i = 1; i <= 10; i++) { + jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().id(new StreamEntryID(i + "-0")), map); + } + assertEquals(10L, jedis.xlen(STREAM_KEY_1)); + + Map streamQuery = singletonMap(STREAM_KEY_1, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(5), streamQuery); + + // Add new entry with limit and KEEP_REFERENCES mode (limit requires approximate trimming) + StreamEntryID newId = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams() + .id(new StreamEntryID("11-0")) + .maxLen(5) + .approximateTrimming() // Required for limit to work + .limit(2) // Limit the number of entries to examine for trimming + .trimmingMode(StreamTrimMode.KEEP_REFERENCES), map); + assertNotNull(newId); + + // With limit, trimming may be less aggressive + long streamLen = jedis.xlen(STREAM_KEY_1); + assertTrue(streamLen >= 5); // Should be at least 5, but may be more due to limit + + // PEL should preserve references + List pendingAfter = jedis.xpending(STREAM_KEY_1, GROUP_NAME, XPendingParams.xPendingParams().count(10)); + assertEquals(5, pendingAfter.size()); // All read messages should remain in PEL + } + + // ========== XACK Command Tests ========== + + @Test + public void xackBasic() { + setUpTestStream(); + + // Add a message to the stream + StreamEntryID messageId = jedis.xadd(STREAM_KEY_1, StreamEntryID.NEW_ENTRY, HASH_1); + assertNotNull(messageId); + + // Consumer group already created in setUpTestStream(), just read message + Map streams = singletonMap(STREAM_KEY_1, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + List>> messages = jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, + XReadGroupParams.xReadGroupParams().count(1), streams); + + assertEquals(1, messages.size()); + assertEquals(1, messages.get(0).getValue().size()); + StreamEntryID readMessageId = messages.get(0).getValue().get(0).getID(); + + // Test XACK + long acked = jedis.xack(STREAM_KEY_1, GROUP_NAME, readMessageId); + assertEquals(1L, acked); + } + + @Test + public void xackMultipleMessages() { + setUpTestStream(); + + // Add multiple messages + StreamEntryID id1 = jedis.xadd(STREAM_KEY_1, new StreamEntryID("1-0"), HASH_1); + StreamEntryID id2 = jedis.xadd(STREAM_KEY_1, new StreamEntryID("2-0"), HASH_2); + + // Consumer group already created in setUpTestStream(), just read messages + Map streams = singletonMap(STREAM_KEY_1, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + List>> messages = jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, + XReadGroupParams.xReadGroupParams().count(2), streams); + + assertEquals(1, messages.size()); + assertEquals(2, messages.get(0).getValue().size()); + + // Test XACK with multiple IDs + StreamEntryID readId1 = messages.get(0).getValue().get(0).getID(); + StreamEntryID readId2 = messages.get(0).getValue().get(1).getID(); + long acked = jedis.xack(STREAM_KEY_1, GROUP_NAME, readId1, readId2); + assertEquals(2L, acked); + } + + @Test + public void xackNonExistentMessage() { + setUpTestStream(); + + // Consumer group already created in setUpTestStream() + // Test XACK with non-existent message ID + StreamEntryID nonExistentId = new StreamEntryID("999-0"); + long acked = jedis.xack(STREAM_KEY_1, GROUP_NAME, nonExistentId); + assertEquals(0L, acked); // Should return 0 for non-existent message + } + + // ========== XDEL Command Tests ========== + + @Test + public void xdelBasic() { + setUpTestStream(); + + // Add test entries + StreamEntryID id1 = jedis.xadd(STREAM_KEY_1, new StreamEntryID("1-0"), HASH_1); + StreamEntryID id2 = jedis.xadd(STREAM_KEY_1, new StreamEntryID("2-0"), HASH_2); + assertEquals(2L, jedis.xlen(STREAM_KEY_1)); + + // Test XDEL with single ID + long deleted = jedis.xdel(STREAM_KEY_1, id1); + assertEquals(1L, deleted); + assertEquals(1L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void xdelMultipleEntries() { + setUpTestStream(); + + // Add test entries + StreamEntryID id1 = jedis.xadd(STREAM_KEY_1, new StreamEntryID("1-0"), HASH_1); + StreamEntryID id2 = jedis.xadd(STREAM_KEY_1, new StreamEntryID("2-0"), HASH_2); + StreamEntryID id3 = jedis.xadd(STREAM_KEY_1, new StreamEntryID("3-0"), HASH_1); + assertEquals(3L, jedis.xlen(STREAM_KEY_1)); + + // Test XDEL with multiple IDs + long deleted = jedis.xdel(STREAM_KEY_1, id1, id3); + assertEquals(2L, deleted); + assertEquals(1L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void xdelNonExistentEntries() { + setUpTestStream(); + + // Add one entry + StreamEntryID id1 = jedis.xadd(STREAM_KEY_1, new StreamEntryID("1-0"), HASH_1); + assertEquals(1L, jedis.xlen(STREAM_KEY_1)); + + // Test XDEL with mix of existing and non-existent IDs + StreamEntryID nonExistentId = new StreamEntryID("999-0"); + long deleted = jedis.xdel(STREAM_KEY_1, id1, nonExistentId); + assertEquals(1L, deleted); // Should only delete the existing entry + assertEquals(0L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void xdelEmptyStream() { + setUpTestStream(); + + // Test XDEL on empty stream + StreamEntryID nonExistentId = new StreamEntryID("1-0"); + long deleted = jedis.xdel(STREAM_KEY_1, nonExistentId); + assertEquals(0L, deleted); + } + + // ========== XACKDEL Command Tests ========== + + @Test + public void xackdelBasic() { + setUpTestStream(); + + // Add a message to the stream + StreamEntryID messageId = jedis.xadd(STREAM_KEY_1, new StreamEntryID("1-0"), HASH_1); + assertNotNull(messageId); + + // Consumer group already created in setUpTestStream(), read message + Map streams = singletonMap(STREAM_KEY_1, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + List>> messages = jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, + XReadGroupParams.xReadGroupParams().count(1), streams); + + assertEquals(1, messages.size()); + assertEquals(1, messages.get(0).getValue().size()); + StreamEntryID readMessageId = messages.get(0).getValue().get(0).getID(); + + // Test XACKDEL - should acknowledge and delete the message + List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, readMessageId); + assertThat(results, hasSize(1)); + assertEquals(StreamTrimResult.DELETED, results.get(0)); + + // Verify message is deleted from stream + assertEquals(0L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void xackdelWithTrimMode() { + setUpTestStream(); + + // Add multiple messages + StreamEntryID id1 = jedis.xadd(STREAM_KEY_1, new StreamEntryID("1-0"), HASH_1); + StreamEntryID id2 = jedis.xadd(STREAM_KEY_1, new StreamEntryID("2-0"), HASH_2); + + // Consumer group already created, read messages + Map streams = singletonMap(STREAM_KEY_1, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + List>> messages = jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, + XReadGroupParams.xReadGroupParams().count(2), streams); + + assertEquals(1, messages.size()); + assertEquals(2, messages.get(0).getValue().size()); + + // Test XACKDEL with KEEP_REFERENCES mode + StreamEntryID readId1 = messages.get(0).getValue().get(0).getID(); + List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, StreamTrimMode.KEEP_REFERENCES, readId1); + assertThat(results, hasSize(1)); + assertEquals(StreamTrimResult.DELETED, results.get(0)); + + // Verify one message is deleted + assertEquals(1L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void xackdelUnreadMessages() { + setUpTestStream(); + + // Add test entries but don't read them + StreamEntryID id1 = jedis.xadd(STREAM_KEY_1, new StreamEntryID("1-0"), HASH_1); + + // Test XACKDEL on unread messages - should return NOT_FOUND for PEL + List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, id1); + + assertThat(results, hasSize(1)); + // Should return NOT_FOUND because message was never read by the consumer group + assertEquals(StreamTrimResult.NOT_FOUND, results.get(0)); + + // Stream should still contain the message + assertEquals(1L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void xackdelMultipleMessages() { + setUpTestStream(); + + // Add multiple messages + StreamEntryID id1 = jedis.xadd(STREAM_KEY_1, new StreamEntryID("1-0"), HASH_1); + StreamEntryID id2 = jedis.xadd(STREAM_KEY_1, new StreamEntryID("2-0"), HASH_2); + StreamEntryID id3 = jedis.xadd(STREAM_KEY_1, new StreamEntryID("3-0"), HASH_1); + + // Read all messages + Map streams = singletonMap(STREAM_KEY_1, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + List>> messages = jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, + XReadGroupParams.xReadGroupParams().count(3), streams); + + assertEquals(1, messages.size()); + assertEquals(3, messages.get(0).getValue().size()); + + // Test XACKDEL with multiple IDs + StreamEntryID readId1 = messages.get(0).getValue().get(0).getID(); + StreamEntryID readId2 = messages.get(0).getValue().get(1).getID(); + List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, readId1, readId2); + assertThat(results, hasSize(2)); + assertEquals(StreamTrimResult.DELETED, results.get(0)); + assertEquals(StreamTrimResult.DELETED, results.get(1)); + + // Verify two messages are deleted + assertEquals(1L, jedis.xlen(STREAM_KEY_1)); + } + + // ========== XDELEX Command Tests ========== + + @Test + public void xdelexBasic() { + setUpTestStream(); + + // Add test entries + StreamEntryID id1 = jedis.xadd(STREAM_KEY_1, new StreamEntryID("1-0"), HASH_1); + StreamEntryID id2 = jedis.xadd(STREAM_KEY_1, new StreamEntryID("2-0"), HASH_2); + assertEquals(2L, jedis.xlen(STREAM_KEY_1)); + + // Test basic XDELEX without parameters (should behave like XDEL with KEEP_REFERENCES) + List results = jedis.xdelex(STREAM_KEY_1, id1); + assertThat(results, hasSize(1)); + assertEquals(StreamTrimResult.DELETED, results.get(0)); + + // Verify entry is deleted from stream + assertEquals(1L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void xdelexWithTrimMode() { + setUpTestStream(); + + // Add test entries + StreamEntryID id1 = jedis.xadd(STREAM_KEY_1, new StreamEntryID("1-0"), HASH_1); + StreamEntryID id2 = jedis.xadd(STREAM_KEY_1, new StreamEntryID("2-0"), HASH_2); + + // Test XDELEX with DELETE_REFERENCES mode + List results = jedis.xdelex(STREAM_KEY_1, StreamTrimMode.DELETE_REFERENCES, id1); + assertThat(results, hasSize(1)); + assertEquals(StreamTrimResult.DELETED, results.get(0)); + + // Verify entry is deleted from stream + assertEquals(1L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void xdelexMultipleEntries() { + setUpTestStream(); + + // Add test entries + StreamEntryID id1 = jedis.xadd(STREAM_KEY_1, new StreamEntryID("1-0"), HASH_1); + StreamEntryID id2 = jedis.xadd(STREAM_KEY_1, new StreamEntryID("2-0"), HASH_2); + StreamEntryID id3 = jedis.xadd(STREAM_KEY_1, new StreamEntryID("3-0"), HASH_1); + assertEquals(3L, jedis.xlen(STREAM_KEY_1)); + + // Test XDELEX with multiple IDs + List results = jedis.xdelex(STREAM_KEY_1, id1, id3); + assertThat(results, hasSize(2)); + assertEquals(StreamTrimResult.DELETED, results.get(0)); + assertEquals(StreamTrimResult.DELETED, results.get(1)); + + // Verify two entries are deleted + assertEquals(1L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void xdelexNonExistentEntries() { + setUpTestStream(); + + // Add one entry + StreamEntryID id1 = jedis.xadd(STREAM_KEY_1, new StreamEntryID("1-0"), HASH_1); + assertEquals(1L, jedis.xlen(STREAM_KEY_1)); + + // Test XDELEX with mix of existing and non-existent IDs + StreamEntryID nonExistentId = new StreamEntryID("999-0"); + List results = jedis.xdelex(STREAM_KEY_1, id1, nonExistentId); + assertThat(results, hasSize(2)); + assertEquals(StreamTrimResult.DELETED, results.get(0)); // Existing entry + assertEquals(StreamTrimResult.NOT_FOUND, results.get(1)); // Non-existent entry + + // Verify existing entry is deleted + assertEquals(0L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void xdelexWithConsumerGroups() { + setUpTestStream(); + + // Add test entries + StreamEntryID id1 = jedis.xadd(STREAM_KEY_1, new StreamEntryID("1-0"), HASH_1); + StreamEntryID id2 = jedis.xadd(STREAM_KEY_1, new StreamEntryID("2-0"), HASH_2); + + // Read messages to add them to PEL + Map streams = singletonMap(STREAM_KEY_1, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + List>> messages = jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, + XReadGroupParams.xReadGroupParams().count(2), streams); + + assertEquals(1, messages.size()); + assertEquals(2, messages.get(0).getValue().size()); + + // Acknowledge only the first message + StreamEntryID readId1 = messages.get(0).getValue().get(0).getID(); + StreamEntryID readId2 = messages.get(0).getValue().get(1).getID(); + jedis.xack(STREAM_KEY_1, GROUP_NAME, readId1); + + // Test XDELEX with ACKNOWLEDGED mode - should only delete acknowledged entries + List results = jedis.xdelex(STREAM_KEY_1, StreamTrimMode.ACKNOWLEDGED, readId1, readId2); + assertThat(results, hasSize(2)); + assertEquals(StreamTrimResult.DELETED, results.get(0)); // id1 was acknowledged + assertEquals(StreamTrimResult.ACKNOWLEDGED_NOT_DELETED, results.get(1)); // id2 not acknowledged + + // Verify only acknowledged entry was deleted + assertEquals(1L, jedis.xlen(STREAM_KEY_1)); + } + + @Test + public void xdelexEmptyStream() { + setUpTestStream(); + + // Test XDELEX on empty stream + StreamEntryID nonExistentId = new StreamEntryID("1-0"); + List results = jedis.xdelex(STREAM_KEY_1, nonExistentId); + assertThat(results, hasSize(1)); + assertEquals(StreamTrimResult.NOT_FOUND, results.get(0)); + } +} diff --git a/src/test/java/redis/clients/jedis/commands/unified/cluster/ClusterStreamsCommandsTest.java b/src/test/java/redis/clients/jedis/commands/unified/cluster/ClusterStreamsCommandsTest.java new file mode 100644 index 0000000000..85137cdb98 --- /dev/null +++ b/src/test/java/redis/clients/jedis/commands/unified/cluster/ClusterStreamsCommandsTest.java @@ -0,0 +1,28 @@ +package redis.clients.jedis.commands.unified.cluster; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; +import redis.clients.jedis.RedisProtocol; +import redis.clients.jedis.commands.unified.StreamsCommandsTestBase; + +@ParameterizedClass +@MethodSource("redis.clients.jedis.commands.CommandsTestsParameters#respVersions") +public class ClusterStreamsCommandsTest extends StreamsCommandsTestBase { + + public ClusterStreamsCommandsTest(RedisProtocol protocol) { + super(protocol); + } + + @Override + protected void setUpTestClient() { + jedis = ClusterCommandsTestHelper.getCleanCluster(protocol); + } + + @AfterEach + public void tearDown() { + jedis.close(); + ClusterCommandsTestHelper.clearClusterData(); + } + +} diff --git a/src/test/java/redis/clients/jedis/commands/unified/pooled/PooledStreamsCommandsTest.java b/src/test/java/redis/clients/jedis/commands/unified/pooled/PooledStreamsCommandsTest.java new file mode 100644 index 0000000000..970e47a6f3 --- /dev/null +++ b/src/test/java/redis/clients/jedis/commands/unified/pooled/PooledStreamsCommandsTest.java @@ -0,0 +1,28 @@ +package redis.clients.jedis.commands.unified.pooled; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; +import redis.clients.jedis.RedisProtocol; +import redis.clients.jedis.commands.unified.StreamsCommandsTestBase; + +@ParameterizedClass +@MethodSource("redis.clients.jedis.commands.CommandsTestsParameters#respVersions") +public class PooledStreamsCommandsTest extends StreamsCommandsTestBase { + + public PooledStreamsCommandsTest(RedisProtocol protocol) { + super(protocol); + } + + @Override + public void setUpTestClient() { + jedis = PooledCommandsTestHelper.getPooled(protocol); + PooledCommandsTestHelper.clearData(); + } + + @AfterEach + public void tearDown() { + jedis.close(); + } + +} diff --git a/src/test/java/redis/clients/jedis/resps/StreamEntryDeletionResultTest.java b/src/test/java/redis/clients/jedis/resps/StreamEntryDeletionResultTest.java new file mode 100644 index 0000000000..ff49f7cdd0 --- /dev/null +++ b/src/test/java/redis/clients/jedis/resps/StreamEntryDeletionResultTest.java @@ -0,0 +1,48 @@ +package redis.clients.jedis.resps; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class StreamEntryDeletionResultTest { + + @Test + public void testFromCode() { + assertEquals(StreamTrimResult.NOT_FOUND, StreamTrimResult.fromCode(-1)); + assertEquals(StreamTrimResult.DELETED, StreamTrimResult.fromCode(1)); + assertEquals(StreamTrimResult.ACKNOWLEDGED_NOT_DELETED, StreamTrimResult.fromCode(2)); + } + + @Test + public void testFromCodeInvalid() { + assertThrows(IllegalArgumentException.class, () -> StreamTrimResult.fromCode(0)); + assertThrows(IllegalArgumentException.class, () -> StreamTrimResult.fromCode(3)); + assertThrows(IllegalArgumentException.class, () -> StreamTrimResult.fromCode(-2)); + } + + @Test + public void testFromLong() { + assertEquals(StreamTrimResult.NOT_FOUND, StreamTrimResult.fromLong(-1L)); + assertEquals(StreamTrimResult.DELETED, StreamTrimResult.fromLong(1L)); + assertEquals(StreamTrimResult.ACKNOWLEDGED_NOT_DELETED, StreamTrimResult.fromLong(2L)); + } + + @Test + public void testFromLongNull() { + assertThrows(IllegalArgumentException.class, () -> StreamTrimResult.fromLong(null)); + } + + @Test + public void testGetCode() { + assertEquals(-1, StreamTrimResult.NOT_FOUND.getCode()); + assertEquals(1, StreamTrimResult.DELETED.getCode()); + assertEquals(2, StreamTrimResult.ACKNOWLEDGED_NOT_DELETED.getCode()); + } + + @Test + public void testToString() { + assertEquals("NOT_FOUND(-1)", StreamTrimResult.NOT_FOUND.toString()); + assertEquals("DELETED(1)", StreamTrimResult.DELETED.toString()); + assertEquals("ACKNOWLEDGED_NOT_DELETED(2)", StreamTrimResult.ACKNOWLEDGED_NOT_DELETED.toString()); + } +} From 842a628121fd97ff3e69ca2193f9d1af03450fb5 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 25 Jul 2025 11:16:02 +0200 Subject: [PATCH 2/5] Clean up tests --- .../jedis/StreamsBinaryCommandsTest.java | 13 + .../commands/jedis/StreamsCommandsTest.java | 16 +- .../StreamsBinaryCommandsTestBase.java | 13 + .../unified/StreamsCommandsTestBase.java | 380 +++++++----------- .../ClusterStreamsBinaryCommandsTest.java | 15 + .../cluster/ClusterStreamsCommandsTest.java | 15 + .../PooledStreamsBinaryCommandsTest.java | 11 + .../pooled/PooledStreamsCommandsTest.java | 11 + 8 files changed, 246 insertions(+), 228 deletions(-) diff --git a/src/test/java/redis/clients/jedis/commands/jedis/StreamsBinaryCommandsTest.java b/src/test/java/redis/clients/jedis/commands/jedis/StreamsBinaryCommandsTest.java index 8034453388..12e576a76b 100644 --- a/src/test/java/redis/clients/jedis/commands/jedis/StreamsBinaryCommandsTest.java +++ b/src/test/java/redis/clients/jedis/commands/jedis/StreamsBinaryCommandsTest.java @@ -1,5 +1,6 @@ package redis.clients.jedis.commands.jedis; +import io.redis.test.annotations.SinceRedisVersion; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedClass; @@ -262,6 +263,7 @@ public void xreadGroupBinaryAsMapMultipleStreams() { // ========== XACKDEL Command Tests ========== @Test + @SinceRedisVersion("8.1.240") public void testXackdel() { // Add a message to the stream byte[] messageId = jedis.xadd(STREAM_KEY_1, new XAddParams().id("1-0"), HASH_1); @@ -287,6 +289,7 @@ public void testXackdel() { } @Test + @SinceRedisVersion("8.1.240") public void testXackdelWithTrimMode() { // Add multiple messages jedis.xadd(STREAM_KEY_1, new XAddParams().id("1-0"), HASH_1); @@ -312,6 +315,7 @@ public void testXackdelWithTrimMode() { } @Test + @SinceRedisVersion("8.1.240") public void testXackdelUnreadMessages() { // Add test entries but don't read them byte[] id1 = jedis.xadd(STREAM_KEY_1, new XAddParams().id("1-0"), HASH_1); @@ -328,6 +332,7 @@ public void testXackdelUnreadMessages() { } @Test + @SinceRedisVersion("8.1.240") public void testXackdelMultipleMessages() { // Add multiple messages jedis.xadd(STREAM_KEY_1, new XAddParams().id("1-0"), HASH_1); @@ -358,6 +363,7 @@ public void testXackdelMultipleMessages() { // ========== XDELEX Command Tests ========== @Test + @SinceRedisVersion("8.1.240") public void testXdelex() { // Add test entries byte[] id1 = jedis.xadd(STREAM_KEY_1, new XAddParams().id("1-0"), HASH_1); @@ -374,6 +380,7 @@ public void testXdelex() { } @Test + @SinceRedisVersion("8.1.240") public void testXdelexWithTrimMode() { // Add test entries byte[] id1 = jedis.xadd(STREAM_KEY_1, new XAddParams().id("1-0"), HASH_1); @@ -389,6 +396,7 @@ public void testXdelexWithTrimMode() { } @Test + @SinceRedisVersion("8.1.240") public void testXdelexMultipleEntries() { // Add test entries byte[] id1 = jedis.xadd(STREAM_KEY_1, new XAddParams().id("1-0"), HASH_1); @@ -407,6 +415,7 @@ public void testXdelexMultipleEntries() { } @Test + @SinceRedisVersion("8.1.240") public void testXdelexNonExistentEntries() { // Add one entry byte[] id1 = jedis.xadd(STREAM_KEY_1, new XAddParams().id("1-0"), HASH_1); @@ -424,6 +433,7 @@ public void testXdelexNonExistentEntries() { } @Test + @SinceRedisVersion("8.1.240") public void testXdelexWithConsumerGroups() { // Add test entries jedis.xadd(STREAM_KEY_1, new XAddParams().id("1-0"), HASH_1); @@ -454,6 +464,7 @@ public void testXdelexWithConsumerGroups() { } @Test + @SinceRedisVersion("8.1.240") public void testXdelexEmptyStream() { // Test XDELEX on empty stream byte[] nonExistentId = "1-0".getBytes(); @@ -465,6 +476,7 @@ public void testXdelexEmptyStream() { // ========== XTRIM Command Tests with trimmingMode ========== @Test + @SinceRedisVersion("8.1.240") public void testXtrimWithKeepReferences() { // Add test entries for (int i = 1; i <= 5; i++) { @@ -483,6 +495,7 @@ public void testXtrimWithKeepReferences() { } @Test + @SinceRedisVersion("8.1.240") public void testXtrimWithAcknowledged() { // Add test entries for (int i = 1; i <= 5; i++) { diff --git a/src/test/java/redis/clients/jedis/commands/jedis/StreamsCommandsTest.java b/src/test/java/redis/clients/jedis/commands/jedis/StreamsCommandsTest.java index 54fc4fd772..c254913499 100644 --- a/src/test/java/redis/clients/jedis/commands/jedis/StreamsCommandsTest.java +++ b/src/test/java/redis/clients/jedis/commands/jedis/StreamsCommandsTest.java @@ -199,6 +199,7 @@ public void xaddParamsId() { } @Test + @SinceRedisVersion("8.1.240") public void xaddWithTrimmingModeKeepReferences() { String streamKey = "xadd-trim-keep-ref-stream"; String groupName = "test-group"; @@ -236,6 +237,7 @@ public void xaddWithTrimmingModeKeepReferences() { } @Test + @SinceRedisVersion("8.1.240") public void xaddWithTrimmingModeDeleteReferences() { String streamKey = "xadd-trim-del-ref-stream"; String groupName = "test-group"; @@ -274,6 +276,7 @@ public void xaddWithTrimmingModeDeleteReferences() { } @Test + @SinceRedisVersion("8.1.240") public void xaddWithTrimmingModeAcknowledged() { String streamKey = "xadd-trim-acked-stream"; String groupName = "test-group"; @@ -318,6 +321,7 @@ public void xaddWithTrimmingModeAcknowledged() { } @Test + @SinceRedisVersion("8.1.240") public void xaddWithMinIdTrimmingModeKeepReferences() { String streamKey = "xadd-minid-keep-ref-stream"; String groupName = "test-group"; @@ -356,6 +360,7 @@ public void xaddWithMinIdTrimmingModeKeepReferences() { } @Test + @SinceRedisVersion("8.1.240") public void xaddWithMinIdTrimmingModeDeleteReferences() { String streamKey = "xadd-minid-del-ref-stream"; String groupName = "test-group"; @@ -395,6 +400,7 @@ public void xaddWithMinIdTrimmingModeDeleteReferences() { } @Test + @SinceRedisVersion("8.1.240") public void xaddWithApproximateTrimmingAndTrimmingMode() { String streamKey = "xadd-approx-trim-stream"; String groupName = "test-group"; @@ -430,6 +436,7 @@ public void xaddWithApproximateTrimmingAndTrimmingMode() { } @Test + @SinceRedisVersion("8.1.240") public void xaddWithExactTrimmingAndTrimmingMode() { String streamKey = "xadd-exact-trim-mode-stream"; String groupName = "test-group"; @@ -465,6 +472,7 @@ public void xaddWithExactTrimmingAndTrimmingMode() { } @Test + @SinceRedisVersion("8.1.240") public void xaddWithLimitAndTrimmingMode() { String streamKey = "xadd-limit-trim-mode-stream"; String groupName = "test-group"; @@ -531,7 +539,7 @@ public void xlen() { @Test public void xrange() { - List range = jedis.xrange("xrange-stream", null, + List range = jedis.xrange("xrange-stream", (StreamEntryID) null, (StreamEntryID) null, Integer.MAX_VALUE); assertEquals(0, range.size()); @@ -539,7 +547,7 @@ public void xrange() { map.put("f1", "v1"); StreamEntryID id1 = jedis.xadd("xrange-stream", (StreamEntryID) null, map); StreamEntryID id2 = jedis.xadd("xrange-stream", (StreamEntryID) null, map); - List range2 = jedis.xrange("xrange-stream", null, + List range2 = jedis.xrange("xrange-stream", (StreamEntryID) null, (StreamEntryID) null, 3); assertEquals(2, range2.size()); assertEquals(range2.get(0).toString(), id1 + " " + map); @@ -560,7 +568,7 @@ public void xrange() { List range7 = jedis.xrange("xrange-stream", id3, id3, 4); assertEquals(1, range7.size()); - List range8 = jedis.xrange("xrange-stream", null, (StreamEntryID) null); + List range8 = jedis.xrange("xrange-stream", (StreamEntryID) null, (StreamEntryID) null); assertEquals(3, range8.size()); range8 = jedis.xrange("xrange-stream", StreamEntryID.MINIMUM_ID, StreamEntryID.MAXIMUM_ID); assertEquals(3, range8.size()); @@ -595,7 +603,7 @@ public void xreadWithParams() { Map map = new HashMap<>(); map.put("f1", "v1"); StreamEntryID id1 = jedis.xadd(key1, (StreamEntryID) null, map); - jedis.xadd(key2, (StreamEntryID) null, map); + StreamEntryID id2 = jedis.xadd(key2, (StreamEntryID) null, map); // Read only a single Stream List>> streams1 = jedis.xread(XReadParams.xReadParams().count(1).block(1), streamQeury1); diff --git a/src/test/java/redis/clients/jedis/commands/unified/StreamsBinaryCommandsTestBase.java b/src/test/java/redis/clients/jedis/commands/unified/StreamsBinaryCommandsTestBase.java index abf30326cd..0b0cdbcce8 100644 --- a/src/test/java/redis/clients/jedis/commands/unified/StreamsBinaryCommandsTestBase.java +++ b/src/test/java/redis/clients/jedis/commands/unified/StreamsBinaryCommandsTestBase.java @@ -1,5 +1,6 @@ package redis.clients.jedis.commands.unified; +import io.redis.test.annotations.SinceRedisVersion; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import redis.clients.jedis.RedisProtocol; @@ -261,6 +262,7 @@ public void xreadGroupBinaryAsMapMultipleStreams() { // ========== XACKDEL Command Tests ========== @Test + @SinceRedisVersion("8.1.240") public void testXackdel() { setUpTestStream(); @@ -288,6 +290,7 @@ public void testXackdel() { } @Test + @SinceRedisVersion("8.1.240") public void testXackdelWithTrimMode() { setUpTestStream(); @@ -315,6 +318,7 @@ public void testXackdelWithTrimMode() { } @Test + @SinceRedisVersion("8.1.240") public void testXackdelUnreadMessages() { setUpTestStream(); @@ -333,6 +337,7 @@ public void testXackdelUnreadMessages() { } @Test + @SinceRedisVersion("8.1.240") public void testXackdelMultipleMessages() { setUpTestStream(); @@ -365,6 +370,7 @@ public void testXackdelMultipleMessages() { // ========== XDELEX Command Tests ========== @Test + @SinceRedisVersion("8.1.240") public void testXdelex() { setUpTestStream(); @@ -383,6 +389,7 @@ public void testXdelex() { } @Test + @SinceRedisVersion("8.1.240") public void testXdelexWithTrimMode() { setUpTestStream(); @@ -400,6 +407,7 @@ public void testXdelexWithTrimMode() { } @Test + @SinceRedisVersion("8.1.240") public void testXdelexMultipleEntries() { setUpTestStream(); @@ -420,6 +428,7 @@ public void testXdelexMultipleEntries() { } @Test + @SinceRedisVersion("8.1.240") public void testXdelexNonExistentEntries() { setUpTestStream(); @@ -439,6 +448,7 @@ public void testXdelexNonExistentEntries() { } @Test + @SinceRedisVersion("8.1.240") public void testXdelexWithConsumerGroups() { setUpTestStream(); @@ -471,6 +481,7 @@ public void testXdelexWithConsumerGroups() { } @Test + @SinceRedisVersion("8.1.240") public void testXdelexEmptyStream() { setUpTestStream(); @@ -484,6 +495,7 @@ public void testXdelexEmptyStream() { // ========== XTRIM Command Tests with trimmingMode ========== @Test + @SinceRedisVersion("8.1.240") public void testXtrimWithKeepReferences() { setUpTestStream(); @@ -504,6 +516,7 @@ public void testXtrimWithKeepReferences() { } @Test + @SinceRedisVersion("8.1.240") public void testXtrimWithAcknowledged() { setUpTestStream(); diff --git a/src/test/java/redis/clients/jedis/commands/unified/StreamsCommandsTestBase.java b/src/test/java/redis/clients/jedis/commands/unified/StreamsCommandsTestBase.java index e1cefa4093..a0f7981a5b 100644 --- a/src/test/java/redis/clients/jedis/commands/unified/StreamsCommandsTestBase.java +++ b/src/test/java/redis/clients/jedis/commands/unified/StreamsCommandsTestBase.java @@ -1,7 +1,10 @@ package redis.clients.jedis.commands.unified; +import io.redis.test.annotations.SinceRedisVersion; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import redis.clients.jedis.RedisProtocol; import redis.clients.jedis.StreamEntryID; import redis.clients.jedis.args.StreamTrimMode; @@ -46,33 +49,17 @@ public StreamsCommandsTestBase(RedisProtocol protocol) { } /** - * Creates a map of stream keys to StreamEntryID objects. - * @param streamOffsets Array of stream key and offset pairs - * @return Map of stream keys to StreamEntryID objects + * Populates a test stream with values using the i-0 format + * @param streamKey The stream key to populate + * @param count Number of entries to add + * @param map Map of field-value pairs for each entry */ - public static Map offsets(Object... streamOffsets) { - if (streamOffsets.length % 2 != 0) { - throw new IllegalArgumentException("Stream offsets must be provided as key-value pairs"); + protected void populateTestStreamWithValues(String streamKey, int count, + Map map) { + for (int i = 1; i <= count; i++) { + jedis.xadd(streamKey, XAddParams.xAddParams().id(new StreamEntryID(i + "-0")), map); } - - Map result = new HashMap<>(); - for (int i = 0; i < streamOffsets.length; i += 2) { - String key = (String) streamOffsets[i]; - Object value = streamOffsets[i + 1]; - - StreamEntryID id; - if (value instanceof String) { - id = new StreamEntryID((String) value); - } else if (value instanceof StreamEntryID) { - id = (StreamEntryID) value; - } else { - throw new IllegalArgumentException("Offset must be a String or StreamEntryID"); - } - - result.put(key, id); - } - - return result; + assertEquals(count, jedis.xlen(streamKey)); } @BeforeEach @@ -119,7 +106,7 @@ public void xaddBasic() { multiFieldHash.put("field1", "value1"); multiFieldHash.put("field2", "value2"); multiFieldHash.put("field3", "value3"); - + StreamEntryID id2 = jedis.xadd(STREAM_KEY_1, StreamEntryID.NEW_ENTRY, multiFieldHash); assertNotNull(id2); assertTrue(id2.compareTo(id1) > 0); @@ -148,13 +135,11 @@ public void xaddWithParams() { setUpTestStream(); // Test XADD with maxLen parameter - for (int i = 1; i <= 5; i++) { - jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().id(new StreamEntryID(i + "-0")), HASH_1); - } - assertEquals(5L, jedis.xlen(STREAM_KEY_1)); + populateTestStreamWithValues(STREAM_KEY_1, 5, HASH_1); // Add with maxLen=3, should trim to 3 entries - StreamEntryID id6 = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().id(new StreamEntryID("6-0")).maxLen(3), HASH_2); + StreamEntryID id6 = jedis.xadd(STREAM_KEY_1, + XAddParams.xAddParams().id(new StreamEntryID("6-0")).maxLen(3), HASH_2); assertNotNull(id6); assertEquals(3L, jedis.xlen(STREAM_KEY_1)); } @@ -173,94 +158,59 @@ public void xaddErrorCases() { } // Test XADD with noMkStream on non-existent stream - StreamEntryID result = jedis.xadd("non-existent-stream", XAddParams.xAddParams().noMkStream(), HASH_1); + StreamEntryID result = jedis.xadd("non-existent-stream", XAddParams.xAddParams().noMkStream(), + HASH_1); assertNull(result); } - @Test - public void xaddWithTrimmingModeKeepReferences() { + @ParameterizedTest + @CsvSource({ "KEEP_REFERENCES,3", "DELETE_REFERENCES,0" }) + @SinceRedisVersion("8.1.240") + public void xaddWithTrimmingMode(StreamTrimMode trimMode, int expected) { setUpTestStream(); Map map = singletonMap("field", "value"); // Add initial entries to the stream - for (int i = 1; i <= 5; i++) { - jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().id(new StreamEntryID(i + "-0")), map); - } - assertEquals(5L, jedis.xlen(STREAM_KEY_1)); + populateTestStreamWithValues(STREAM_KEY_1, 5, map); // Create consumer group and read messages to create PEL entries - Map streamQuery = singletonMap(STREAM_KEY_1, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); - jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3), streamQuery); + Map streamQuery = singletonMap(STREAM_KEY_1, + StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3), + streamQuery); // Verify PEL has entries - List pendingBefore = jedis.xpending(STREAM_KEY_1, GROUP_NAME, XPendingParams.xPendingParams().count(10)); + List pendingBefore = jedis.xpending(STREAM_KEY_1, GROUP_NAME, + XPendingParams.xPendingParams().count(10)); assertEquals(3, pendingBefore.size()); // Add new entry with maxLen=3 and KEEP_REFERENCES mode - StreamEntryID newId = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams() - .id(new StreamEntryID("6-0")) - .maxLen(3) - .trimmingMode(StreamTrimMode.KEEP_REFERENCES), map); + StreamEntryID newId = jedis.xadd(STREAM_KEY_1, + XAddParams.xAddParams().id(new StreamEntryID("6-0")).maxLen(3).trimmingMode(trimMode), map); assertNotNull(newId); // Stream should be trimmed to 3 entries assertEquals(3L, jedis.xlen(STREAM_KEY_1)); - // PEL references should be preserved even though entries were trimmed - List pendingAfter = jedis.xpending(STREAM_KEY_1, GROUP_NAME, XPendingParams.xPendingParams().count(10)); - assertEquals(3, pendingAfter.size()); // PEL entries should still exist - } - - @Test - public void xaddWithTrimmingModeDeleteReferences() { - setUpTestStream(); - Map map = singletonMap("field", "value"); - - // Add initial entries to the stream - for (int i = 1; i <= 5; i++) { - jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().id(new StreamEntryID(i + "-0")), map); - } - assertEquals(5L, jedis.xlen(STREAM_KEY_1)); - - // Create consumer group and read messages to create PEL entries - Map streamQuery = singletonMap(STREAM_KEY_1, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); - jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3), streamQuery); - - // Verify PEL has entries - List pendingBefore = jedis.xpending(STREAM_KEY_1, GROUP_NAME, XPendingParams.xPendingParams().count(10)); - assertEquals(3, pendingBefore.size()); - - // Add new entry with maxLen=3 and DELETE_REFERENCES mode - StreamEntryID newId = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams() - .id(new StreamEntryID("6-0")) - .maxLen(3) - .trimmingMode(StreamTrimMode.DELETE_REFERENCES), map); - assertNotNull(newId); - - // Stream should be trimmed to 3 entries - assertEquals(3L, jedis.xlen(STREAM_KEY_1)); - - // PEL references should be removed for trimmed entries - List pendingAfter = jedis.xpending(STREAM_KEY_1, GROUP_NAME, XPendingParams.xPendingParams().count(10)); - // Only entries that still exist in the stream should remain in PEL - assertTrue(pendingAfter.size() <= 3); + List pendingAfter = jedis.xpending(STREAM_KEY_1, GROUP_NAME, + XPendingParams.xPendingParams().count(10)); + assertEquals(expected, pendingAfter.size()); } @Test + @SinceRedisVersion("8.1.240") public void xaddWithTrimmingModeAcknowledged() { setUpTestStream(); Map map = singletonMap("field", "value"); // Add initial entries to the stream - for (int i = 1; i <= 5; i++) { - jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().id(new StreamEntryID(i + "-0")), map); - } - assertEquals(5L, jedis.xlen(STREAM_KEY_1)); + populateTestStreamWithValues(STREAM_KEY_1, 5, map); // Create consumer group and read messages - Map streamQuery = singletonMap(STREAM_KEY_1, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); - List>> messages = jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, - XReadGroupParams.xReadGroupParams().count(3), streamQuery); + Map streamQuery = singletonMap(STREAM_KEY_1, + StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + List>> messages = jedis.xreadGroup(GROUP_NAME, + CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3), streamQuery); // Acknowledge the first 2 messages StreamEntryID id1 = messages.get(0).getValue().get(0).getID(); @@ -268,23 +218,24 @@ public void xaddWithTrimmingModeAcknowledged() { jedis.xack(STREAM_KEY_1, GROUP_NAME, id1, id2); // Verify PEL state - List pendingBefore = jedis.xpending(STREAM_KEY_1, GROUP_NAME, XPendingParams.xPendingParams().count(10)); + List pendingBefore = jedis.xpending(STREAM_KEY_1, GROUP_NAME, + XPendingParams.xPendingParams().count(10)); assertEquals(1, pendingBefore.size()); // Only 1 unacknowledged message // Add new entry with maxLen=3 and ACKNOWLEDGED mode StreamEntryID newId = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams() - .id(new StreamEntryID("6-0")) - .maxLen(3) - .trimmingMode(StreamTrimMode.ACKNOWLEDGED), map); + .id(new StreamEntryID("6-0")).maxLen(3).trimmingMode(StreamTrimMode.ACKNOWLEDGED), + map); assertNotNull(newId); // Stream length should respect acknowledgment status long streamLen = jedis.xlen(STREAM_KEY_1); - assertTrue(streamLen >= 3); // Should not trim unacknowledged entries aggressively + assertEquals(4, streamLen); // Should not trim unacknowledged entries aggressively // PEL should still contain unacknowledged entries - List pendingAfter = jedis.xpending(STREAM_KEY_1, GROUP_NAME, XPendingParams.xPendingParams().count(10)); - assertTrue(pendingAfter.size() >= 1); // Unacknowledged entries should remain + List pendingAfter = jedis.xpending(STREAM_KEY_1, GROUP_NAME, + XPendingParams.xPendingParams().count(10)); + assertEquals(1, pendingAfter.size()); // Unacknowledged entries should remain } // ========== XTRIM Command Tests ========== @@ -294,10 +245,7 @@ public void xtrimBasic() { setUpTestStream(); // Add test entries - for (int i = 1; i <= 5; i++) { - jedis.xadd(STREAM_KEY_1, new StreamEntryID(i + "-0"), HASH_1); - } - assertEquals(5L, jedis.xlen(STREAM_KEY_1)); + populateTestStreamWithValues(STREAM_KEY_1, 5, HASH_1); // Test basic XTRIM with maxLen long trimmed = jedis.xtrim(STREAM_KEY_1, 3, false); @@ -310,19 +258,17 @@ public void xtrimWithParams() { setUpTestStream(); // Add test entries with specific IDs - for (int i = 1; i <= 5; i++) { - jedis.xadd(STREAM_KEY_1, new StreamEntryID("0-" + i), HASH_1); - } - assertEquals(5L, jedis.xlen(STREAM_KEY_1)); + populateTestStreamWithValues(STREAM_KEY_1, 5, HASH_1); // Test XTRIM with XTrimParams and exact trimming long trimmed = jedis.xtrim(STREAM_KEY_1, XTrimParams.xTrimParams().maxLen(3).exactTrimming()); assertEquals(2L, trimmed); assertEquals(3L, jedis.xlen(STREAM_KEY_1)); - // Test XTRIM with minId - long trimmed2 = jedis.xtrim(STREAM_KEY_1, XTrimParams.xTrimParams().minId("0-4").exactTrimming()); - assertEquals(1L, trimmed2); // Should trim entries with ID < 0-4 + // Test XTRIM with minId - use "4-0" since we have entries 1-0, 2-0, 3-0, 4-0, 5-0 + long trimmed2 = jedis.xtrim(STREAM_KEY_1, + XTrimParams.xTrimParams().minId("4-0").exactTrimming()); + assertEquals(1L, trimmed2); // Should trim entries with ID < 4-0 (only 3-0 should be trimmed) assertEquals(2L, jedis.xlen(STREAM_KEY_1)); } @@ -331,10 +277,7 @@ public void xtrimApproximate() { setUpTestStream(); // Add many entries - for (int i = 1; i <= 10; i++) { - jedis.xadd(STREAM_KEY_1, new StreamEntryID(i + "-0"), HASH_1); - } - assertEquals(10L, jedis.xlen(STREAM_KEY_1)); + populateTestStreamWithValues(STREAM_KEY_1, 10, HASH_1); // Test approximate trimming long trimmed = jedis.xtrim(STREAM_KEY_1, 5, true); @@ -342,98 +285,63 @@ public void xtrimApproximate() { assertTrue(jedis.xlen(STREAM_KEY_1) <= 10); // Should not exceed original length } - @Test - public void xaddWithMinIdTrimmingModeKeepReferences() { - setUpTestStream(); - Map map = singletonMap("field", "value"); - - // Add initial entries with specific IDs - for (int i = 1; i <= 5; i++) { - jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().id(new StreamEntryID("0-" + i)), map); - } - assertEquals(5L, jedis.xlen(STREAM_KEY_1)); - - // Create consumer group and read messages to create PEL entries - Map streamQuery = singletonMap(STREAM_KEY_1, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); - jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3), streamQuery); - - // Verify PEL has entries - List pendingBefore = jedis.xpending(STREAM_KEY_1, GROUP_NAME, XPendingParams.xPendingParams().count(10)); - assertEquals(3, pendingBefore.size()); - - // Add new entry with minId="0-3" and KEEP_REFERENCES mode (should trim entries < 0-3) - StreamEntryID newId = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams() - .id(new StreamEntryID("0-6")) - .minId("0-3") - .trimmingMode(StreamTrimMode.KEEP_REFERENCES), map); - assertNotNull(newId); - - // Stream should have entries >= 0-3 plus the new entry - long streamLen = jedis.xlen(STREAM_KEY_1); - assertTrue(streamLen >= 3); // Should keep entries 0-3, 0-4, 0-5, 0-6 - - // PEL references should be preserved even for trimmed entries - List pendingAfter = jedis.xpending(STREAM_KEY_1, GROUP_NAME, XPendingParams.xPendingParams().count(10)); - assertEquals(3, pendingAfter.size()); // PEL entries should still exist - } - - @Test - public void xaddWithMinIdTrimmingModeDeleteReferences() { + @ParameterizedTest + @CsvSource({ "KEEP_REFERENCES,3", "DELETE_REFERENCES,1" }) + @SinceRedisVersion("8.1.240") + public void xaddWithMinIdTrimmingMode(StreamTrimMode trimMode, int expected) { setUpTestStream(); Map map = singletonMap("field", "value"); // Add initial entries with specific IDs - for (int i = 1; i <= 5; i++) { - jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().id(new StreamEntryID("0-" + i)), map); - } - assertEquals(5L, jedis.xlen(STREAM_KEY_1)); + populateTestStreamWithValues(STREAM_KEY_1, 5, map); // Create consumer group and read messages to create PEL entries - Map streamQuery = singletonMap(STREAM_KEY_1, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); - jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3), streamQuery); + Map streamQuery = singletonMap(STREAM_KEY_1, + StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3), + streamQuery); // Verify PEL has entries - List pendingBefore = jedis.xpending(STREAM_KEY_1, GROUP_NAME, XPendingParams.xPendingParams().count(10)); + List pendingBefore = jedis.xpending(STREAM_KEY_1, GROUP_NAME, + XPendingParams.xPendingParams().count(10)); assertEquals(3, pendingBefore.size()); - // Add new entry with minId="0-3" and DELETE_REFERENCES mode - StreamEntryID newId = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams() - .id(new StreamEntryID("0-6")) - .minId("0-3") - .trimmingMode(StreamTrimMode.DELETE_REFERENCES), map); + // Add new entry with minId="3-0" and specified trimming mode + StreamEntryID newId = jedis.xadd(STREAM_KEY_1, + XAddParams.xAddParams().id(new StreamEntryID("6-0")).minId("3-0").trimmingMode(trimMode), + map); assertNotNull(newId); - // Stream should have entries >= 0-3 plus the new entry + // Stream should have entries >= 3-0 plus the new entry long streamLen = jedis.xlen(STREAM_KEY_1); assertTrue(streamLen >= 3); - // PEL references should be removed for trimmed entries (0-1, 0-2) - List pendingAfter = jedis.xpending(STREAM_KEY_1, GROUP_NAME, XPendingParams.xPendingParams().count(10)); - // Only entries that still exist in the stream should remain in PEL - assertTrue(pendingAfter.size() <= pendingBefore.size()); + // Check PEL entries based on trimming mode + List pendingAfter = jedis.xpending(STREAM_KEY_1, GROUP_NAME, + XPendingParams.xPendingParams().count(10)); + assertEquals(expected, pendingAfter.size()); } @Test + @SinceRedisVersion("8.1.240") public void xaddWithApproximateTrimmingAndTrimmingMode() { setUpTestStream(); Map map = singletonMap("field", "value"); // Add initial entries - for (int i = 1; i <= 10; i++) { - jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().id(new StreamEntryID(i + "-0")), map); - } - assertEquals(10L, jedis.xlen(STREAM_KEY_1)); + populateTestStreamWithValues(STREAM_KEY_1, 10, map); // Create consumer group and read messages - Map streamQuery = singletonMap(STREAM_KEY_1, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); - jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(5), streamQuery); + Map streamQuery = singletonMap(STREAM_KEY_1, + StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(5), + streamQuery); // Add new entry with approximate trimming and KEEP_REFERENCES mode - StreamEntryID newId = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams() - .id(new StreamEntryID("11-0")) - .maxLen(5) - .approximateTrimming() - .trimmingMode(StreamTrimMode.KEEP_REFERENCES), map); + StreamEntryID newId = jedis.xadd(STREAM_KEY_1, + XAddParams.xAddParams().id(new StreamEntryID("11-0")).maxLen(5).approximateTrimming() + .trimmingMode(StreamTrimMode.KEEP_REFERENCES), + map); assertNotNull(newId); // With approximate trimming, the exact length may vary but should be around the target @@ -441,63 +349,67 @@ public void xaddWithApproximateTrimmingAndTrimmingMode() { assertTrue(streamLen >= 5); // Should be approximately 5, but may be more due to approximation // PEL should preserve references - List pendingAfter = jedis.xpending(STREAM_KEY_1, GROUP_NAME, XPendingParams.xPendingParams().count(10)); + List pendingAfter = jedis.xpending(STREAM_KEY_1, GROUP_NAME, + XPendingParams.xPendingParams().count(10)); assertEquals(5, pendingAfter.size()); // All read messages should remain in PEL } @Test + @SinceRedisVersion("8.1.240") public void xaddWithExactTrimmingAndTrimmingMode() { setUpTestStream(); Map map = singletonMap("field", "value"); // Add initial entries - for (int i = 1; i <= 5; i++) { - jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().id(new StreamEntryID(i + "-0")), map); - } - assertEquals(5L, jedis.xlen(STREAM_KEY_1)); + populateTestStreamWithValues(STREAM_KEY_1, 5, map); // Create consumer group and read messages - Map streamQuery = singletonMap(STREAM_KEY_1, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); - jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3), streamQuery); + Map streamQuery = singletonMap(STREAM_KEY_1, + StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3), + streamQuery); // Add new entry with exact trimming and DELETE_REFERENCES mode - StreamEntryID newId = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams() - .id(new StreamEntryID("6-0")) - .maxLen(3) - .exactTrimming() - .trimmingMode(StreamTrimMode.DELETE_REFERENCES), map); + StreamEntryID newId = jedis.xadd(STREAM_KEY_1, + XAddParams.xAddParams().id(new StreamEntryID("6-0")).maxLen(3).exactTrimming() + .trimmingMode(StreamTrimMode.DELETE_REFERENCES), + map); assertNotNull(newId); // With exact trimming, stream should be exactly 3 entries assertEquals(3L, jedis.xlen(STREAM_KEY_1)); // PEL references should be cleaned up for trimmed entries - List pendingAfter = jedis.xpending(STREAM_KEY_1, GROUP_NAME, XPendingParams.xPendingParams().count(10)); + List pendingAfter = jedis.xpending(STREAM_KEY_1, GROUP_NAME, + XPendingParams.xPendingParams().count(10)); // Only entries that still exist in the stream should remain in PEL assertTrue(pendingAfter.size() <= 3); } @Test + @SinceRedisVersion("8.1.240") public void xaddWithLimitAndTrimmingMode() { setUpTestStream(); Map map = singletonMap("field", "value"); // Add initial entries - for (int i = 1; i <= 10; i++) { - jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().id(new StreamEntryID(i + "-0")), map); - } - assertEquals(10L, jedis.xlen(STREAM_KEY_1)); + populateTestStreamWithValues(STREAM_KEY_1, 10, map); - Map streamQuery = singletonMap(STREAM_KEY_1, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); - jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(5), streamQuery); + Map streamQuery = singletonMap(STREAM_KEY_1, + StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(5), + streamQuery); // Add new entry with limit and KEEP_REFERENCES mode (limit requires approximate trimming) - StreamEntryID newId = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams() - .id(new StreamEntryID("11-0")) - .maxLen(5) - .approximateTrimming() // Required for limit to work - .limit(2) // Limit the number of entries to examine for trimming - .trimmingMode(StreamTrimMode.KEEP_REFERENCES), map); + StreamEntryID newId = jedis.xadd(STREAM_KEY_1, + XAddParams.xAddParams().id(new StreamEntryID("11-0")).maxLen(5).approximateTrimming() // Required + // for + // limit + // to + // work + .limit(2) // Limit the number of entries to examine for trimming + .trimmingMode(StreamTrimMode.KEEP_REFERENCES), + map); assertNotNull(newId); // With limit, trimming may be less aggressive @@ -505,7 +417,8 @@ public void xaddWithLimitAndTrimmingMode() { assertTrue(streamLen >= 5); // Should be at least 5, but may be more due to limit // PEL should preserve references - List pendingAfter = jedis.xpending(STREAM_KEY_1, GROUP_NAME, XPendingParams.xPendingParams().count(10)); + List pendingAfter = jedis.xpending(STREAM_KEY_1, GROUP_NAME, + XPendingParams.xPendingParams().count(10)); assertEquals(5, pendingAfter.size()); // All read messages should remain in PEL } @@ -520,9 +433,10 @@ public void xackBasic() { assertNotNull(messageId); // Consumer group already created in setUpTestStream(), just read message - Map streams = singletonMap(STREAM_KEY_1, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); - List>> messages = jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, - XReadGroupParams.xReadGroupParams().count(1), streams); + Map streams = singletonMap(STREAM_KEY_1, + StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + List>> messages = jedis.xreadGroup(GROUP_NAME, + CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(1), streams); assertEquals(1, messages.size()); assertEquals(1, messages.get(0).getValue().size()); @@ -542,9 +456,10 @@ public void xackMultipleMessages() { StreamEntryID id2 = jedis.xadd(STREAM_KEY_1, new StreamEntryID("2-0"), HASH_2); // Consumer group already created in setUpTestStream(), just read messages - Map streams = singletonMap(STREAM_KEY_1, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); - List>> messages = jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, - XReadGroupParams.xReadGroupParams().count(2), streams); + Map streams = singletonMap(STREAM_KEY_1, + StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + List>> messages = jedis.xreadGroup(GROUP_NAME, + CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(2), streams); assertEquals(1, messages.size()); assertEquals(2, messages.get(0).getValue().size()); @@ -628,6 +543,7 @@ public void xdelEmptyStream() { // ========== XACKDEL Command Tests ========== @Test + @SinceRedisVersion("8.1.240") public void xackdelBasic() { setUpTestStream(); @@ -636,9 +552,10 @@ public void xackdelBasic() { assertNotNull(messageId); // Consumer group already created in setUpTestStream(), read message - Map streams = singletonMap(STREAM_KEY_1, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); - List>> messages = jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, - XReadGroupParams.xReadGroupParams().count(1), streams); + Map streams = singletonMap(STREAM_KEY_1, + StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + List>> messages = jedis.xreadGroup(GROUP_NAME, + CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(1), streams); assertEquals(1, messages.size()); assertEquals(1, messages.get(0).getValue().size()); @@ -654,6 +571,7 @@ public void xackdelBasic() { } @Test + @SinceRedisVersion("8.1.240") public void xackdelWithTrimMode() { setUpTestStream(); @@ -662,16 +580,18 @@ public void xackdelWithTrimMode() { StreamEntryID id2 = jedis.xadd(STREAM_KEY_1, new StreamEntryID("2-0"), HASH_2); // Consumer group already created, read messages - Map streams = singletonMap(STREAM_KEY_1, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); - List>> messages = jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, - XReadGroupParams.xReadGroupParams().count(2), streams); + Map streams = singletonMap(STREAM_KEY_1, + StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + List>> messages = jedis.xreadGroup(GROUP_NAME, + CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(2), streams); assertEquals(1, messages.size()); assertEquals(2, messages.get(0).getValue().size()); // Test XACKDEL with KEEP_REFERENCES mode StreamEntryID readId1 = messages.get(0).getValue().get(0).getID(); - List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, StreamTrimMode.KEEP_REFERENCES, readId1); + List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, + StreamTrimMode.KEEP_REFERENCES, readId1); assertThat(results, hasSize(1)); assertEquals(StreamTrimResult.DELETED, results.get(0)); @@ -680,6 +600,7 @@ public void xackdelWithTrimMode() { } @Test + @SinceRedisVersion("8.1.240") public void xackdelUnreadMessages() { setUpTestStream(); @@ -698,6 +619,7 @@ public void xackdelUnreadMessages() { } @Test + @SinceRedisVersion("8.1.240") public void xackdelMultipleMessages() { setUpTestStream(); @@ -707,9 +629,10 @@ public void xackdelMultipleMessages() { StreamEntryID id3 = jedis.xadd(STREAM_KEY_1, new StreamEntryID("3-0"), HASH_1); // Read all messages - Map streams = singletonMap(STREAM_KEY_1, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); - List>> messages = jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, - XReadGroupParams.xReadGroupParams().count(3), streams); + Map streams = singletonMap(STREAM_KEY_1, + StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + List>> messages = jedis.xreadGroup(GROUP_NAME, + CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3), streams); assertEquals(1, messages.size()); assertEquals(3, messages.get(0).getValue().size()); @@ -729,6 +652,7 @@ public void xackdelMultipleMessages() { // ========== XDELEX Command Tests ========== @Test + @SinceRedisVersion("8.1.240") public void xdelexBasic() { setUpTestStream(); @@ -747,6 +671,7 @@ public void xdelexBasic() { } @Test + @SinceRedisVersion("8.1.240") public void xdelexWithTrimMode() { setUpTestStream(); @@ -755,7 +680,8 @@ public void xdelexWithTrimMode() { StreamEntryID id2 = jedis.xadd(STREAM_KEY_1, new StreamEntryID("2-0"), HASH_2); // Test XDELEX with DELETE_REFERENCES mode - List results = jedis.xdelex(STREAM_KEY_1, StreamTrimMode.DELETE_REFERENCES, id1); + List results = jedis.xdelex(STREAM_KEY_1, StreamTrimMode.DELETE_REFERENCES, + id1); assertThat(results, hasSize(1)); assertEquals(StreamTrimResult.DELETED, results.get(0)); @@ -764,6 +690,7 @@ public void xdelexWithTrimMode() { } @Test + @SinceRedisVersion("8.1.240") public void xdelexMultipleEntries() { setUpTestStream(); @@ -784,6 +711,7 @@ public void xdelexMultipleEntries() { } @Test + @SinceRedisVersion("8.1.240") public void xdelexNonExistentEntries() { setUpTestStream(); @@ -803,6 +731,7 @@ public void xdelexNonExistentEntries() { } @Test + @SinceRedisVersion("8.1.240") public void xdelexWithConsumerGroups() { setUpTestStream(); @@ -811,9 +740,10 @@ public void xdelexWithConsumerGroups() { StreamEntryID id2 = jedis.xadd(STREAM_KEY_1, new StreamEntryID("2-0"), HASH_2); // Read messages to add them to PEL - Map streams = singletonMap(STREAM_KEY_1, StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); - List>> messages = jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, - XReadGroupParams.xReadGroupParams().count(2), streams); + Map streams = singletonMap(STREAM_KEY_1, + StreamEntryID.XREADGROUP_UNDELIVERED_ENTRY); + List>> messages = jedis.xreadGroup(GROUP_NAME, + CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(2), streams); assertEquals(1, messages.size()); assertEquals(2, messages.get(0).getValue().size()); @@ -824,7 +754,8 @@ public void xdelexWithConsumerGroups() { jedis.xack(STREAM_KEY_1, GROUP_NAME, readId1); // Test XDELEX with ACKNOWLEDGED mode - should only delete acknowledged entries - List results = jedis.xdelex(STREAM_KEY_1, StreamTrimMode.ACKNOWLEDGED, readId1, readId2); + List results = jedis.xdelex(STREAM_KEY_1, StreamTrimMode.ACKNOWLEDGED, + readId1, readId2); assertThat(results, hasSize(2)); assertEquals(StreamTrimResult.DELETED, results.get(0)); // id1 was acknowledged assertEquals(StreamTrimResult.ACKNOWLEDGED_NOT_DELETED, results.get(1)); // id2 not acknowledged @@ -834,6 +765,7 @@ public void xdelexWithConsumerGroups() { } @Test + @SinceRedisVersion("8.1.240") public void xdelexEmptyStream() { setUpTestStream(); diff --git a/src/test/java/redis/clients/jedis/commands/unified/cluster/ClusterStreamsBinaryCommandsTest.java b/src/test/java/redis/clients/jedis/commands/unified/cluster/ClusterStreamsBinaryCommandsTest.java index e651c1a4c1..086b3416b8 100644 --- a/src/test/java/redis/clients/jedis/commands/unified/cluster/ClusterStreamsBinaryCommandsTest.java +++ b/src/test/java/redis/clients/jedis/commands/unified/cluster/ClusterStreamsBinaryCommandsTest.java @@ -1,15 +1,30 @@ package redis.clients.jedis.commands.unified.cluster; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.params.ParameterizedClass; import org.junit.jupiter.params.provider.MethodSource; +import redis.clients.jedis.DefaultJedisClientConfig; +import redis.clients.jedis.HostAndPorts; import redis.clients.jedis.RedisProtocol; import redis.clients.jedis.commands.unified.StreamsBinaryCommandsTestBase; +import redis.clients.jedis.util.EnabledOnCommandCondition; +import redis.clients.jedis.util.RedisVersionCondition; @ParameterizedClass @MethodSource("redis.clients.jedis.commands.CommandsTestsParameters#respVersions") public class ClusterStreamsBinaryCommandsTest extends StreamsBinaryCommandsTestBase { + @RegisterExtension + public RedisVersionCondition versionCondition = new RedisVersionCondition( + HostAndPorts.getStableClusterServers().get(0), + DefaultJedisClientConfig.builder().password("cluster").build()); + + @RegisterExtension + public EnabledOnCommandCondition enabledOnCommandCondition = new EnabledOnCommandCondition( + HostAndPorts.getStableClusterServers().get(0), + DefaultJedisClientConfig.builder().password("cluster").build()); + public ClusterStreamsBinaryCommandsTest(RedisProtocol protocol) { super(protocol); } diff --git a/src/test/java/redis/clients/jedis/commands/unified/cluster/ClusterStreamsCommandsTest.java b/src/test/java/redis/clients/jedis/commands/unified/cluster/ClusterStreamsCommandsTest.java index 85137cdb98..b65b478621 100644 --- a/src/test/java/redis/clients/jedis/commands/unified/cluster/ClusterStreamsCommandsTest.java +++ b/src/test/java/redis/clients/jedis/commands/unified/cluster/ClusterStreamsCommandsTest.java @@ -1,15 +1,30 @@ package redis.clients.jedis.commands.unified.cluster; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.params.ParameterizedClass; import org.junit.jupiter.params.provider.MethodSource; +import redis.clients.jedis.DefaultJedisClientConfig; +import redis.clients.jedis.HostAndPorts; import redis.clients.jedis.RedisProtocol; import redis.clients.jedis.commands.unified.StreamsCommandsTestBase; +import redis.clients.jedis.util.EnabledOnCommandCondition; +import redis.clients.jedis.util.RedisVersionCondition; @ParameterizedClass @MethodSource("redis.clients.jedis.commands.CommandsTestsParameters#respVersions") public class ClusterStreamsCommandsTest extends StreamsCommandsTestBase { + @RegisterExtension + public RedisVersionCondition versionCondition = new RedisVersionCondition( + HostAndPorts.getStableClusterServers().get(0), + DefaultJedisClientConfig.builder().password("cluster").build()); + + @RegisterExtension + public EnabledOnCommandCondition enabledOnCommandCondition = new EnabledOnCommandCondition( + HostAndPorts.getStableClusterServers().get(0), + DefaultJedisClientConfig.builder().password("cluster").build()); + public ClusterStreamsCommandsTest(RedisProtocol protocol) { super(protocol); } diff --git a/src/test/java/redis/clients/jedis/commands/unified/pooled/PooledStreamsBinaryCommandsTest.java b/src/test/java/redis/clients/jedis/commands/unified/pooled/PooledStreamsBinaryCommandsTest.java index 93f4279b4a..b967e2a4e1 100644 --- a/src/test/java/redis/clients/jedis/commands/unified/pooled/PooledStreamsBinaryCommandsTest.java +++ b/src/test/java/redis/clients/jedis/commands/unified/pooled/PooledStreamsBinaryCommandsTest.java @@ -1,15 +1,26 @@ package redis.clients.jedis.commands.unified.pooled; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.params.ParameterizedClass; import org.junit.jupiter.params.provider.MethodSource; import redis.clients.jedis.RedisProtocol; import redis.clients.jedis.commands.unified.StreamsBinaryCommandsTestBase; +import redis.clients.jedis.util.EnabledOnCommandCondition; +import redis.clients.jedis.util.RedisVersionCondition; @ParameterizedClass @MethodSource("redis.clients.jedis.commands.CommandsTestsParameters#respVersions") public class PooledStreamsBinaryCommandsTest extends StreamsBinaryCommandsTestBase { + @RegisterExtension + public RedisVersionCondition versionCondition = new RedisVersionCondition( + PooledCommandsTestHelper.nodeInfo); + + @RegisterExtension + public EnabledOnCommandCondition enabledOnCommandCondition = new EnabledOnCommandCondition( + PooledCommandsTestHelper.nodeInfo); + public PooledStreamsBinaryCommandsTest(RedisProtocol protocol) { super(protocol); } diff --git a/src/test/java/redis/clients/jedis/commands/unified/pooled/PooledStreamsCommandsTest.java b/src/test/java/redis/clients/jedis/commands/unified/pooled/PooledStreamsCommandsTest.java index 970e47a6f3..24a3aaf889 100644 --- a/src/test/java/redis/clients/jedis/commands/unified/pooled/PooledStreamsCommandsTest.java +++ b/src/test/java/redis/clients/jedis/commands/unified/pooled/PooledStreamsCommandsTest.java @@ -1,15 +1,26 @@ package redis.clients.jedis.commands.unified.pooled; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.params.ParameterizedClass; import org.junit.jupiter.params.provider.MethodSource; import redis.clients.jedis.RedisProtocol; import redis.clients.jedis.commands.unified.StreamsCommandsTestBase; +import redis.clients.jedis.util.EnabledOnCommandCondition; +import redis.clients.jedis.util.RedisVersionCondition; @ParameterizedClass @MethodSource("redis.clients.jedis.commands.CommandsTestsParameters#respVersions") public class PooledStreamsCommandsTest extends StreamsCommandsTestBase { + @RegisterExtension + public RedisVersionCondition versionCondition = new RedisVersionCondition( + PooledCommandsTestHelper.nodeInfo); + + @RegisterExtension + public EnabledOnCommandCondition enabledOnCommandCondition = new EnabledOnCommandCondition( + PooledCommandsTestHelper.nodeInfo); + public PooledStreamsCommandsTest(RedisProtocol protocol) { super(protocol); } From 218f6fd55abccd4bceff61311b0681708813b3b5 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 25 Jul 2025 12:40:56 +0200 Subject: [PATCH 3/5] Rename StreamTrimMode and StreamTrimResult StreamTrimMode -> StreamDeletionPolicy StreamTrimResult -> StreamEntryDeletionResult --- .../redis/clients/jedis/BuilderFactory.java | 12 ++-- .../redis/clients/jedis/CommandObjects.java | 16 ++--- src/main/java/redis/clients/jedis/Jedis.java | 16 ++--- .../redis/clients/jedis/PipeliningBase.java | 16 ++--- .../redis/clients/jedis/UnifiedJedis.java | 16 ++--- ...rimMode.java => StreamDeletionPolicy.java} | 6 +- .../jedis/commands/StreamBinaryCommands.java | 12 ++-- .../jedis/commands/StreamCommands.java | 10 +-- .../StreamPipelineBinaryCommands.java | 12 ++-- .../commands/StreamPipelineCommands.java | 10 +-- .../clients/jedis/params/XAddParams.java | 8 +-- .../clients/jedis/params/XTrimParams.java | 8 +-- ...lt.java => StreamEntryDeletionResult.java} | 8 +-- .../jedis/StreamsBinaryCommandsTest.java | 58 ++++++++-------- .../commands/jedis/StreamsCommandsTest.java | 18 ++--- .../StreamsBinaryCommandsTestBase.java | 58 ++++++++-------- .../unified/StreamsCommandsTestBase.java | 66 +++++++++---------- .../resps/StreamEntryDeletionResultTest.java | 32 ++++----- 18 files changed, 193 insertions(+), 189 deletions(-) rename src/main/java/redis/clients/jedis/args/{StreamTrimMode.java => StreamDeletionPolicy.java} (81%) rename src/main/java/redis/clients/jedis/resps/{StreamTrimResult.java => StreamEntryDeletionResult.java} (91%) diff --git a/src/main/java/redis/clients/jedis/BuilderFactory.java b/src/main/java/redis/clients/jedis/BuilderFactory.java index d3a3455c2b..585d4bfd29 100644 --- a/src/main/java/redis/clients/jedis/BuilderFactory.java +++ b/src/main/java/redis/clients/jedis/BuilderFactory.java @@ -1261,13 +1261,13 @@ public List build(Object data) { } }; - public static final Builder STREAM_ENTRY_DELETION_RESULT = new Builder() { + public static final Builder STREAM_ENTRY_DELETION_RESULT = new Builder() { @Override - public StreamTrimResult build(Object data) { + public StreamEntryDeletionResult build(Object data) { if (data == null) { return null; } - return StreamTrimResult.fromLong((Long) data); + return StreamEntryDeletionResult.fromLong((Long) data); } @Override @@ -1276,15 +1276,15 @@ public String toString() { } }; - public static final Builder> STREAM_ENTRY_DELETION_RESULT_LIST = new Builder>() { + public static final Builder> STREAM_ENTRY_DELETION_RESULT_LIST = new Builder>() { @Override @SuppressWarnings("unchecked") - public List build(Object data) { + public List build(Object data) { if (data == null) { return null; } List objectList = (List) data; - List responses = new ArrayList<>(objectList.size()); + List responses = new ArrayList<>(objectList.size()); for (Object object : objectList) { responses.add(STREAM_ENTRY_DELETION_RESULT.build(object)); } diff --git a/src/main/java/redis/clients/jedis/CommandObjects.java b/src/main/java/redis/clients/jedis/CommandObjects.java index 7f43f44bf0..5533ecf1f5 100644 --- a/src/main/java/redis/clients/jedis/CommandObjects.java +++ b/src/main/java/redis/clients/jedis/CommandObjects.java @@ -2626,11 +2626,11 @@ public final CommandObject xack(String key, String group, StreamEntryID... return new CommandObject<>(commandArguments(XACK).key(key).add(group).addObjects((Object[]) ids), BuilderFactory.LONG); } - public final CommandObject> xackdel(String key, String group, StreamEntryID... ids) { + public final CommandObject> xackdel(String key, String group, StreamEntryID... ids) { return new CommandObject<>(commandArguments(XACKDEL).key(key).add(group).add("IDS").add(ids.length).addObjects((Object[]) ids), BuilderFactory.STREAM_ENTRY_DELETION_RESULT_LIST); } - public final CommandObject> xackdel(String key, String group, StreamTrimMode trimMode, StreamEntryID... ids) { + public final CommandObject> xackdel(String key, String group, StreamDeletionPolicy trimMode, StreamEntryID... ids) { return new CommandObject<>(commandArguments(XACKDEL).key(key).add(group).add(trimMode).add("IDS").add(ids.length).addObjects((Object[]) ids), BuilderFactory.STREAM_ENTRY_DELETION_RESULT_LIST); } @@ -2638,11 +2638,11 @@ public final CommandObject xack(byte[] key, byte[] group, byte[]... ids) { return new CommandObject<>(commandArguments(XACK).key(key).add(group).addObjects((Object[]) ids), BuilderFactory.LONG); } - public final CommandObject> xackdel(byte[] key, byte[] group, byte[]... ids) { + public final CommandObject> xackdel(byte[] key, byte[] group, byte[]... ids) { return new CommandObject<>(commandArguments(XACKDEL).key(key).add(group).add("IDS").add(ids.length).addObjects((Object[]) ids), BuilderFactory.STREAM_ENTRY_DELETION_RESULT_LIST); } - public final CommandObject> xackdel(byte[] key, byte[] group, StreamTrimMode trimMode, byte[]... ids) { + public final CommandObject> xackdel(byte[] key, byte[] group, StreamDeletionPolicy trimMode, byte[]... ids) { return new CommandObject<>(commandArguments(XACKDEL).key(key).add(group).add(trimMode).add("IDS").add(ids.length).addObjects((Object[]) ids), BuilderFactory.STREAM_ENTRY_DELETION_RESULT_LIST); } @@ -2703,11 +2703,11 @@ public final CommandObject xdel(String key, StreamEntryID... ids) { return new CommandObject<>(commandArguments(XDEL).key(key).addObjects((Object[]) ids), BuilderFactory.LONG); } - public final CommandObject> xdelex(String key, StreamEntryID... ids) { + public final CommandObject> xdelex(String key, StreamEntryID... ids) { return new CommandObject<>(commandArguments(XDELEX).key(key).add("IDS").add(ids.length).addObjects((Object[]) ids), BuilderFactory.STREAM_ENTRY_DELETION_RESULT_LIST); } - public final CommandObject> xdelex(String key, StreamTrimMode trimMode, StreamEntryID... ids) { + public final CommandObject> xdelex(String key, StreamDeletionPolicy trimMode, StreamEntryID... ids) { return new CommandObject<>(commandArguments(XDELEX).key(key).add(trimMode).add("IDS").add(ids.length).addObjects((Object[]) ids), BuilderFactory.STREAM_ENTRY_DELETION_RESULT_LIST); } @@ -2726,11 +2726,11 @@ public final CommandObject xdel(byte[] key, byte[]... ids) { return new CommandObject<>(commandArguments(XDEL).key(key).addObjects((Object[]) ids), BuilderFactory.LONG); } - public final CommandObject> xdelex(byte[] key, byte[]... ids) { + public final CommandObject> xdelex(byte[] key, byte[]... ids) { return new CommandObject<>(commandArguments(XDELEX).key(key).add("IDS").add(ids.length).addObjects((Object[]) ids), BuilderFactory.STREAM_ENTRY_DELETION_RESULT_LIST); } - public final CommandObject> xdelex(byte[] key, StreamTrimMode trimMode, byte[]... ids) { + public final CommandObject> xdelex(byte[] key, StreamDeletionPolicy trimMode, byte[]... ids) { return new CommandObject<>(commandArguments(XDELEX).key(key).add(trimMode).add("IDS").add(ids.length).addObjects((Object[]) ids), BuilderFactory.STREAM_ENTRY_DELETION_RESULT_LIST); } diff --git a/src/main/java/redis/clients/jedis/Jedis.java b/src/main/java/redis/clients/jedis/Jedis.java index b92da0a5ca..6d50b8814c 100644 --- a/src/main/java/redis/clients/jedis/Jedis.java +++ b/src/main/java/redis/clients/jedis/Jedis.java @@ -4867,13 +4867,13 @@ public long xack(byte[] key, byte[] group, byte[]... ids) { } @Override - public List xackdel(byte[] key, byte[] group, byte[]... ids) { + public List xackdel(byte[] key, byte[] group, byte[]... ids) { checkIsInMultiOrPipeline(); return connection.executeCommand(commandObjects.xackdel(key, group, ids)); } @Override - public List xackdel(byte[] key, byte[] group, StreamTrimMode trimMode, byte[]... ids) { + public List xackdel(byte[] key, byte[] group, StreamDeletionPolicy trimMode, byte[]... ids) { checkIsInMultiOrPipeline(); return connection.executeCommand(commandObjects.xackdel(key, group, trimMode, ids)); } @@ -4915,13 +4915,13 @@ public long xdel(byte[] key, byte[]... ids) { } @Override - public List xdelex(byte[] key, byte[]... ids) { + public List xdelex(byte[] key, byte[]... ids) { checkIsInMultiOrPipeline(); return connection.executeCommand(commandObjects.xdelex(key, ids)); } @Override - public List xdelex(byte[] key, StreamTrimMode trimMode, byte[]... ids) { + public List xdelex(byte[] key, StreamDeletionPolicy trimMode, byte[]... ids) { checkIsInMultiOrPipeline(); return connection.executeCommand(commandObjects.xdelex(key, trimMode, ids)); } @@ -9702,13 +9702,13 @@ public long xack(final String key, final String group, final StreamEntryID... id } @Override - public List xackdel(final String key, final String group, final StreamEntryID... ids) { + public List xackdel(final String key, final String group, final StreamEntryID... ids) { checkIsInMultiOrPipeline(); return connection.executeCommand(commandObjects.xackdel(key, group, ids)); } @Override - public List xackdel(final String key, final String group, final StreamTrimMode trimMode, final StreamEntryID... ids) { + public List xackdel(final String key, final String group, final StreamDeletionPolicy trimMode, final StreamEntryID... ids) { checkIsInMultiOrPipeline(); return connection.executeCommand(commandObjects.xackdel(key, group, trimMode, ids)); } @@ -9751,13 +9751,13 @@ public long xdel(final String key, final StreamEntryID... ids) { } @Override - public List xdelex(final String key, final StreamEntryID... ids) { + public List xdelex(final String key, final StreamEntryID... ids) { checkIsInMultiOrPipeline(); return connection.executeCommand(commandObjects.xdelex(key, ids)); } @Override - public List xdelex(final String key, final StreamTrimMode trimMode, final StreamEntryID... ids) { + public List xdelex(final String key, final StreamDeletionPolicy trimMode, final StreamEntryID... ids) { checkIsInMultiOrPipeline(); return connection.executeCommand(commandObjects.xdelex(key, trimMode, ids)); } diff --git a/src/main/java/redis/clients/jedis/PipeliningBase.java b/src/main/java/redis/clients/jedis/PipeliningBase.java index dc2481c62e..7274e8c7b3 100644 --- a/src/main/java/redis/clients/jedis/PipeliningBase.java +++ b/src/main/java/redis/clients/jedis/PipeliningBase.java @@ -1553,12 +1553,12 @@ public Response xack(String key, String group, StreamEntryID... ids) { } @Override - public Response> xackdel(String key, String group, StreamEntryID... ids) { + public Response> xackdel(String key, String group, StreamEntryID... ids) { return appendCommand(commandObjects.xackdel(key, group, ids)); } @Override - public Response> xackdel(String key, String group, StreamTrimMode trimMode, StreamEntryID... ids) { + public Response> xackdel(String key, String group, StreamDeletionPolicy trimMode, StreamEntryID... ids) { return appendCommand(commandObjects.xackdel(key, group, trimMode, ids)); } @@ -1603,12 +1603,12 @@ public Response xdel(String key, StreamEntryID... ids) { } @Override - public Response> xdelex(String key, StreamEntryID... ids) { + public Response> xdelex(String key, StreamEntryID... ids) { return appendCommand(commandObjects.xdelex(key, ids)); } @Override - public Response> xdelex(String key, StreamTrimMode trimMode, StreamEntryID... ids) { + public Response> xdelex(String key, StreamDeletionPolicy trimMode, StreamEntryID... ids) { return appendCommand(commandObjects.xdelex(key, trimMode, ids)); } @@ -3285,12 +3285,12 @@ public Response xack(byte[] key, byte[] group, byte[]... ids) { } @Override - public Response> xackdel(byte[] key, byte[] group, byte[]... ids) { + public Response> xackdel(byte[] key, byte[] group, byte[]... ids) { return appendCommand(commandObjects.xackdel(key, group, ids)); } @Override - public Response> xackdel(byte[] key, byte[] group, StreamTrimMode trimMode, byte[]... ids) { + public Response> xackdel(byte[] key, byte[] group, StreamDeletionPolicy trimMode, byte[]... ids) { return appendCommand(commandObjects.xackdel(key, group, trimMode, ids)); } @@ -3325,12 +3325,12 @@ public Response xdel(byte[] key, byte[]... ids) { } @Override - public Response> xdelex(byte[] key, byte[]... ids) { + public Response> xdelex(byte[] key, byte[]... ids) { return appendCommand(commandObjects.xdelex(key, ids)); } @Override - public Response> xdelex(byte[] key, StreamTrimMode trimMode, byte[]... ids) { + public Response> xdelex(byte[] key, StreamDeletionPolicy trimMode, byte[]... ids) { return appendCommand(commandObjects.xdelex(key, trimMode, ids)); } diff --git a/src/main/java/redis/clients/jedis/UnifiedJedis.java b/src/main/java/redis/clients/jedis/UnifiedJedis.java index d94a075c7b..e0522f1eda 100644 --- a/src/main/java/redis/clients/jedis/UnifiedJedis.java +++ b/src/main/java/redis/clients/jedis/UnifiedJedis.java @@ -3202,12 +3202,12 @@ public long xack(String key, String group, StreamEntryID... ids) { } @Override - public List xackdel(String key, String group, StreamEntryID... ids) { + public List xackdel(String key, String group, StreamEntryID... ids) { return executeCommand(commandObjects.xackdel(key, group, ids)); } @Override - public List xackdel(String key, String group, StreamTrimMode trimMode, StreamEntryID... ids) { + public List xackdel(String key, String group, StreamDeletionPolicy trimMode, StreamEntryID... ids) { return executeCommand(commandObjects.xackdel(key, group, trimMode, ids)); } @@ -3252,12 +3252,12 @@ public long xdel(String key, StreamEntryID... ids) { } @Override - public List xdelex(String key, StreamEntryID... ids) { + public List xdelex(String key, StreamEntryID... ids) { return executeCommand(commandObjects.xdelex(key, ids)); } @Override - public List xdelex(String key, StreamTrimMode trimMode, StreamEntryID... ids) { + public List xdelex(String key, StreamDeletionPolicy trimMode, StreamEntryID... ids) { return executeCommand(commandObjects.xdelex(key, trimMode, ids)); } @@ -3377,12 +3377,12 @@ public long xack(byte[] key, byte[] group, byte[]... ids) { } @Override - public List xackdel(byte[] key, byte[] group, byte[]... ids) { + public List xackdel(byte[] key, byte[] group, byte[]... ids) { return executeCommand(commandObjects.xackdel(key, group, ids)); } @Override - public List xackdel(byte[] key, byte[] group, StreamTrimMode trimMode, byte[]... ids) { + public List xackdel(byte[] key, byte[] group, StreamDeletionPolicy trimMode, byte[]... ids) { return executeCommand(commandObjects.xackdel(key, group, trimMode, ids)); } @@ -3417,12 +3417,12 @@ public long xdel(byte[] key, byte[]... ids) { } @Override - public List xdelex(byte[] key, byte[]... ids) { + public List xdelex(byte[] key, byte[]... ids) { return executeCommand(commandObjects.xdelex(key, ids)); } @Override - public List xdelex(byte[] key, StreamTrimMode trimMode, byte[]... ids) { + public List xdelex(byte[] key, StreamDeletionPolicy trimMode, byte[]... ids) { return executeCommand(commandObjects.xdelex(key, trimMode, ids)); } diff --git a/src/main/java/redis/clients/jedis/args/StreamTrimMode.java b/src/main/java/redis/clients/jedis/args/StreamDeletionPolicy.java similarity index 81% rename from src/main/java/redis/clients/jedis/args/StreamTrimMode.java rename to src/main/java/redis/clients/jedis/args/StreamDeletionPolicy.java index b17c06f7dd..bd065346fa 100644 --- a/src/main/java/redis/clients/jedis/args/StreamTrimMode.java +++ b/src/main/java/redis/clients/jedis/args/StreamDeletionPolicy.java @@ -3,10 +3,10 @@ import redis.clients.jedis.util.SafeEncoder; /** - * Trim strategy for stream commands that handle consumer group references. + * Deletion policy for stream commands that handle consumer group references. * Used with XDELEX, XACKDEL, and enhanced XADD/XTRIM commands. */ -public enum StreamTrimMode implements Rawable { +public enum StreamDeletionPolicy implements Rawable { /** * Preserves existing references to entries in all consumer groups' PEL. @@ -27,7 +27,7 @@ public enum StreamTrimMode implements Rawable { private final byte[] raw; - StreamTrimMode(String redisParamName) { + StreamDeletionPolicy(String redisParamName) { raw = SafeEncoder.encode(redisParamName); } diff --git a/src/main/java/redis/clients/jedis/commands/StreamBinaryCommands.java b/src/main/java/redis/clients/jedis/commands/StreamBinaryCommands.java index b4b3f3dcad..c2d3fcbb2e 100644 --- a/src/main/java/redis/clients/jedis/commands/StreamBinaryCommands.java +++ b/src/main/java/redis/clients/jedis/commands/StreamBinaryCommands.java @@ -4,10 +4,10 @@ import java.util.Map; import redis.clients.jedis.StreamEntryID; -import redis.clients.jedis.args.StreamTrimMode; +import redis.clients.jedis.args.StreamDeletionPolicy; import redis.clients.jedis.params.*; import redis.clients.jedis.resps.StreamEntryBinary; -import redis.clients.jedis.resps.StreamTrimResult; +import redis.clients.jedis.resps.StreamEntryDeletionResult; public interface StreamBinaryCommands { @@ -32,12 +32,12 @@ default byte[] xadd(byte[] key, Map hash, XAddParams params) { /** * XACKDEL key group [KEEPREF | DELREF | ACKED] IDS numids id [id ...] */ - List xackdel(byte[] key, byte[] group, byte[]... ids); + List xackdel(byte[] key, byte[] group, byte[]... ids); /** * XACKDEL key group [KEEPREF | DELREF | ACKED] IDS numids id [id ...] */ - List xackdel(byte[] key, byte[] group, StreamTrimMode trimMode, byte[]... ids); + List xackdel(byte[] key, byte[] group, StreamDeletionPolicy trimMode, byte[]... ids); String xgroupCreate(byte[] key, byte[] groupName, byte[] id, boolean makeStream); @@ -54,12 +54,12 @@ default byte[] xadd(byte[] key, Map hash, XAddParams params) { /** * XDELEX key [KEEPREF | DELREF | ACKED] IDS numids id [id ...] */ - List xdelex(byte[] key, byte[]... ids); + List xdelex(byte[] key, byte[]... ids); /** * XDELEX key [KEEPREF | DELREF | ACKED] IDS numids id [id ...] */ - List xdelex(byte[] key, StreamTrimMode trimMode, byte[]... ids); + List xdelex(byte[] key, StreamDeletionPolicy trimMode, byte[]... ids); long xtrim(byte[] key, long maxLen, boolean approximateLength); diff --git a/src/main/java/redis/clients/jedis/commands/StreamCommands.java b/src/main/java/redis/clients/jedis/commands/StreamCommands.java index 5ba92ca9f7..0a61e34811 100644 --- a/src/main/java/redis/clients/jedis/commands/StreamCommands.java +++ b/src/main/java/redis/clients/jedis/commands/StreamCommands.java @@ -4,7 +4,7 @@ import java.util.Map; import redis.clients.jedis.StreamEntryID; -import redis.clients.jedis.args.StreamTrimMode; +import redis.clients.jedis.args.StreamDeletionPolicy; import redis.clients.jedis.params.*; import redis.clients.jedis.resps.*; @@ -104,14 +104,14 @@ default StreamEntryID xadd(String key, Map hash, XAddParams para * Combines XACK and XDEL functionalities. Acknowledges specified message IDs * in the given consumer group and attempts to delete corresponding stream entries. */ - List xackdel(String key, String group, StreamEntryID... ids); + List xackdel(String key, String group, StreamEntryID... ids); /** * XACKDEL key group [KEEPREF | DELREF | ACKED] IDS numids id [id ...] * Combines XACK and XDEL functionalities. Acknowledges specified message IDs * in the given consumer group and attempts to delete corresponding stream entries. */ - List xackdel(String key, String group, StreamTrimMode trimMode, StreamEntryID... ids); + List xackdel(String key, String group, StreamDeletionPolicy trimMode, StreamEntryID... ids); /** * {@code XGROUP CREATE key groupName } @@ -148,14 +148,14 @@ default StreamEntryID xadd(String key, Map hash, XAddParams para * Extended XDEL command with enhanced control over message entry deletion * with respect to consumer groups. */ - List xdelex(String key, StreamEntryID... ids); + List xdelex(String key, StreamEntryID... ids); /** * XDELEX key [KEEPREF | DELREF | ACKED] IDS numids id [id ...] * Extended XDEL command with enhanced control over message entry deletion * with respect to consumer groups. */ - List xdelex(String key, StreamTrimMode trimMode, StreamEntryID... ids); + List xdelex(String key, StreamDeletionPolicy trimMode, StreamEntryID... ids); /** * XTRIM key MAXLEN [~] count diff --git a/src/main/java/redis/clients/jedis/commands/StreamPipelineBinaryCommands.java b/src/main/java/redis/clients/jedis/commands/StreamPipelineBinaryCommands.java index f863f8cfb1..bd6b36b1b0 100644 --- a/src/main/java/redis/clients/jedis/commands/StreamPipelineBinaryCommands.java +++ b/src/main/java/redis/clients/jedis/commands/StreamPipelineBinaryCommands.java @@ -5,10 +5,10 @@ import redis.clients.jedis.Response; import redis.clients.jedis.StreamEntryID; -import redis.clients.jedis.args.StreamTrimMode; +import redis.clients.jedis.args.StreamDeletionPolicy; import redis.clients.jedis.params.*; import redis.clients.jedis.resps.StreamEntryBinary; -import redis.clients.jedis.resps.StreamTrimResult; +import redis.clients.jedis.resps.StreamEntryDeletionResult; public interface StreamPipelineBinaryCommands { @@ -30,9 +30,9 @@ default Response xadd(byte[] key, Map hash, XAddParams p Response xack(byte[] key, byte[] group, byte[]... ids); - Response> xackdel(byte[] key, byte[] group, byte[]... ids); + Response> xackdel(byte[] key, byte[] group, byte[]... ids); - Response> xackdel(byte[] key, byte[] group, StreamTrimMode trimMode, byte[]... ids); + Response> xackdel(byte[] key, byte[] group, StreamDeletionPolicy trimMode, byte[]... ids); Response xgroupCreate(byte[] key, byte[] groupName, byte[] id, boolean makeStream); @@ -46,9 +46,9 @@ default Response xadd(byte[] key, Map hash, XAddParams p Response xdel(byte[] key, byte[]... ids); - Response> xdelex(byte[] key, byte[]... ids); + Response> xdelex(byte[] key, byte[]... ids); - Response> xdelex(byte[] key, StreamTrimMode trimMode, byte[]... ids); + Response> xdelex(byte[] key, StreamDeletionPolicy trimMode, byte[]... ids); Response xtrim(byte[] key, long maxLen, boolean approximateLength); diff --git a/src/main/java/redis/clients/jedis/commands/StreamPipelineCommands.java b/src/main/java/redis/clients/jedis/commands/StreamPipelineCommands.java index 5b37669f19..e3346363d4 100644 --- a/src/main/java/redis/clients/jedis/commands/StreamPipelineCommands.java +++ b/src/main/java/redis/clients/jedis/commands/StreamPipelineCommands.java @@ -5,7 +5,7 @@ import redis.clients.jedis.Response; import redis.clients.jedis.StreamEntryID; -import redis.clients.jedis.args.StreamTrimMode; +import redis.clients.jedis.args.StreamDeletionPolicy; import redis.clients.jedis.params.*; import redis.clients.jedis.resps.*; @@ -95,12 +95,12 @@ default Response xadd(String key, Map hash, XAddP /** * XACKDEL key group [KEEPREF | DELREF | ACKED] IDS numids id [id ...] */ - Response> xackdel(String key, String group, StreamEntryID... ids); + Response> xackdel(String key, String group, StreamEntryID... ids); /** * XACKDEL key group [KEEPREF | DELREF | ACKED] IDS numids id [id ...] */ - Response> xackdel(String key, String group, StreamTrimMode trimMode, StreamEntryID... ids); + Response> xackdel(String key, String group, StreamDeletionPolicy trimMode, StreamEntryID... ids); /** * {@code XGROUP CREATE key groupName } @@ -145,12 +145,12 @@ default Response xadd(String key, Map hash, XAddP /** * XDELEX key [KEEPREF | DELREF | ACKED] IDS numids id [id ...] */ - Response> xdelex(String key, StreamEntryID... ids); + Response> xdelex(String key, StreamEntryID... ids); /** * XDELEX key [KEEPREF | DELREF | ACKED] IDS numids id [id ...] */ - Response> xdelex(String key, StreamTrimMode trimMode, StreamEntryID... ids); + Response> xdelex(String key, StreamDeletionPolicy trimMode, StreamEntryID... ids); /** * XTRIM key MAXLEN [~] count diff --git a/src/main/java/redis/clients/jedis/params/XAddParams.java b/src/main/java/redis/clients/jedis/params/XAddParams.java index f4e294e82b..db6c4deb3a 100644 --- a/src/main/java/redis/clients/jedis/params/XAddParams.java +++ b/src/main/java/redis/clients/jedis/params/XAddParams.java @@ -6,7 +6,7 @@ import redis.clients.jedis.StreamEntryID; import redis.clients.jedis.args.Rawable; import redis.clients.jedis.args.RawableFactory; -import redis.clients.jedis.args.StreamTrimMode; +import redis.clients.jedis.args.StreamDeletionPolicy; import java.util.Objects; @@ -26,7 +26,7 @@ public class XAddParams implements IParams { private Long limit; - private StreamTrimMode trimMode; + private StreamDeletionPolicy trimMode; public static XAddParams xAddParams() { return new XAddParams(); @@ -86,11 +86,11 @@ public XAddParams limit(long limit) { /** * When trimming, defines desired behaviour for handling consumer group references. - * see {@link StreamTrimMode} for details. + * see {@link StreamDeletionPolicy} for details. * * @return XAddParams */ - public XAddParams trimmingMode(StreamTrimMode trimMode) { + public XAddParams trimmingMode(StreamDeletionPolicy trimMode) { this.trimMode = trimMode; return this; } diff --git a/src/main/java/redis/clients/jedis/params/XTrimParams.java b/src/main/java/redis/clients/jedis/params/XTrimParams.java index d6487388f4..a6c4e8d148 100644 --- a/src/main/java/redis/clients/jedis/params/XTrimParams.java +++ b/src/main/java/redis/clients/jedis/params/XTrimParams.java @@ -3,7 +3,7 @@ import redis.clients.jedis.CommandArguments; import redis.clients.jedis.Protocol; import redis.clients.jedis.Protocol.Keyword; -import redis.clients.jedis.args.StreamTrimMode; +import redis.clients.jedis.args.StreamDeletionPolicy; import java.util.Objects; @@ -19,7 +19,7 @@ public class XTrimParams implements IParams { private Long limit; - private StreamTrimMode trimMode; + private StreamDeletionPolicy trimMode; public static XTrimParams xTrimParams() { return new XTrimParams(); @@ -53,11 +53,11 @@ public XTrimParams limit(long limit) { /** * Defines desired behaviour for handling consumer group references. - * see {@link StreamTrimMode} for details. + * see {@link StreamDeletionPolicy} for details. * * @return XAddParams */ - public XTrimParams trimmingMode(StreamTrimMode trimMode) { + public XTrimParams trimmingMode(StreamDeletionPolicy trimMode) { this.trimMode = trimMode; return this; } diff --git a/src/main/java/redis/clients/jedis/resps/StreamTrimResult.java b/src/main/java/redis/clients/jedis/resps/StreamEntryDeletionResult.java similarity index 91% rename from src/main/java/redis/clients/jedis/resps/StreamTrimResult.java rename to src/main/java/redis/clients/jedis/resps/StreamEntryDeletionResult.java index 72e158eab7..bf48cd6349 100644 --- a/src/main/java/redis/clients/jedis/resps/StreamTrimResult.java +++ b/src/main/java/redis/clients/jedis/resps/StreamEntryDeletionResult.java @@ -6,7 +6,7 @@ * - DELETED (1): Entry was deleted/acknowledged and deleted * - ACKNOWLEDGED_NOT_DELETED (2): Entry was acknowledged but not deleted (still has dangling references) */ -public enum StreamTrimResult { +public enum StreamEntryDeletionResult { /** * The stream entry ID doesn't exist in the stream. @@ -28,7 +28,7 @@ public enum StreamTrimResult { private final int code; - StreamTrimResult(int code) { + StreamEntryDeletionResult(int code) { this.code = code; } @@ -48,7 +48,7 @@ public int getCode() { * @return the corresponding StreamEntryDeletionResult * @throws IllegalArgumentException if the code is not recognized */ - public static StreamTrimResult fromCode(int code) { + public static StreamEntryDeletionResult fromCode(int code) { switch (code) { case -1: return NOT_FOUND; @@ -68,7 +68,7 @@ public static StreamTrimResult fromCode(int code) { * @return the corresponding StreamEntryDeletionResult * @throws IllegalArgumentException if the value is null or not recognized */ - public static StreamTrimResult fromLong(Long value) { + public static StreamEntryDeletionResult fromLong(Long value) { if (value == null) { throw new IllegalArgumentException("Stream entry deletion result value cannot be null"); } diff --git a/src/test/java/redis/clients/jedis/commands/jedis/StreamsBinaryCommandsTest.java b/src/test/java/redis/clients/jedis/commands/jedis/StreamsBinaryCommandsTest.java index 12e576a76b..63ba4111df 100644 --- a/src/test/java/redis/clients/jedis/commands/jedis/StreamsBinaryCommandsTest.java +++ b/src/test/java/redis/clients/jedis/commands/jedis/StreamsBinaryCommandsTest.java @@ -7,14 +7,14 @@ import org.junit.jupiter.params.provider.MethodSource; import redis.clients.jedis.RedisProtocol; import redis.clients.jedis.StreamEntryID; -import redis.clients.jedis.args.StreamTrimMode; +import redis.clients.jedis.args.StreamDeletionPolicy; import redis.clients.jedis.exceptions.JedisDataException; import redis.clients.jedis.params.XAddParams; import redis.clients.jedis.params.XReadGroupParams; import redis.clients.jedis.params.XReadParams; import redis.clients.jedis.params.XTrimParams; import redis.clients.jedis.resps.StreamEntryBinary; -import redis.clients.jedis.resps.StreamTrimResult; +import redis.clients.jedis.resps.StreamEntryDeletionResult; import java.util.ArrayList; import java.util.HashMap; @@ -280,9 +280,9 @@ public void testXackdel() { byte[] readMessageId = messages.get(0).getValue().get(0).getID().toString().getBytes(); // Test XACKDEL - should acknowledge and delete the message - List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, readMessageId); + List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, readMessageId); assertThat(results, hasSize(1)); - assertEquals(StreamTrimResult.DELETED, results.get(0)); + assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); // Verify message is deleted from stream assertEquals(0L, jedis.xlen(STREAM_KEY_1)); @@ -306,9 +306,9 @@ public void testXackdelWithTrimMode() { // Test XACKDEL with KEEP_REFERENCES mode byte[] readId1 = messages.get(0).getValue().get(0).getID().toString().getBytes(); - List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, StreamTrimMode.KEEP_REFERENCES, readId1); + List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, StreamDeletionPolicy.KEEP_REFERENCES, readId1); assertThat(results, hasSize(1)); - assertEquals(StreamTrimResult.DELETED, results.get(0)); + assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); // Verify one message is deleted assertEquals(1L, jedis.xlen(STREAM_KEY_1)); @@ -321,11 +321,11 @@ public void testXackdelUnreadMessages() { byte[] id1 = jedis.xadd(STREAM_KEY_1, new XAddParams().id("1-0"), HASH_1); // Test XACKDEL on unread messages - should return NOT_FOUND for PEL - List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, id1); + List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, id1); assertThat(results, hasSize(1)); // Should return NOT_FOUND because message was never read by the consumer group - assertEquals(StreamTrimResult.NOT_FOUND, results.get(0)); + assertEquals(StreamEntryDeletionResult.NOT_FOUND, results.get(0)); // Stream should still contain the message assertEquals(1L, jedis.xlen(STREAM_KEY_1)); @@ -351,10 +351,10 @@ public void testXackdelMultipleMessages() { // Test XACKDEL with multiple IDs byte[] readId1 = messages.get(0).getValue().get(0).getID().toString().getBytes(); byte[] readId2 = messages.get(0).getValue().get(1).getID().toString().getBytes(); - List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, readId1, readId2); + List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, readId1, readId2); assertThat(results, hasSize(2)); - assertEquals(StreamTrimResult.DELETED, results.get(0)); - assertEquals(StreamTrimResult.DELETED, results.get(1)); + assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); + assertEquals(StreamEntryDeletionResult.DELETED, results.get(1)); // Verify two messages are deleted assertEquals(1L, jedis.xlen(STREAM_KEY_1)); @@ -371,9 +371,9 @@ public void testXdelex() { assertEquals(2L, jedis.xlen(STREAM_KEY_1)); // Test basic XDELEX without parameters (should behave like XDEL with KEEP_REFERENCES) - List results = jedis.xdelex(STREAM_KEY_1, id1); + List results = jedis.xdelex(STREAM_KEY_1, id1); assertThat(results, hasSize(1)); - assertEquals(StreamTrimResult.DELETED, results.get(0)); + assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); // Verify entry is deleted from stream assertEquals(1L, jedis.xlen(STREAM_KEY_1)); @@ -387,9 +387,9 @@ public void testXdelexWithTrimMode() { jedis.xadd(STREAM_KEY_1, new XAddParams().id("2-0"), HASH_2); // Test XDELEX with DELETE_REFERENCES mode - List results = jedis.xdelex(STREAM_KEY_1, StreamTrimMode.DELETE_REFERENCES, id1); + List results = jedis.xdelex(STREAM_KEY_1, StreamDeletionPolicy.DELETE_REFERENCES, id1); assertThat(results, hasSize(1)); - assertEquals(StreamTrimResult.DELETED, results.get(0)); + assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); // Verify entry is deleted from stream assertEquals(1L, jedis.xlen(STREAM_KEY_1)); @@ -405,10 +405,10 @@ public void testXdelexMultipleEntries() { assertEquals(3L, jedis.xlen(STREAM_KEY_1)); // Test XDELEX with multiple IDs - List results = jedis.xdelex(STREAM_KEY_1, id1, id3); + List results = jedis.xdelex(STREAM_KEY_1, id1, id3); assertThat(results, hasSize(2)); - assertEquals(StreamTrimResult.DELETED, results.get(0)); - assertEquals(StreamTrimResult.DELETED, results.get(1)); + assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); + assertEquals(StreamEntryDeletionResult.DELETED, results.get(1)); // Verify two entries are deleted assertEquals(1L, jedis.xlen(STREAM_KEY_1)); @@ -423,10 +423,10 @@ public void testXdelexNonExistentEntries() { // Test XDELEX with mix of existing and non-existent IDs byte[] nonExistentId = "999-0".getBytes(); - List results = jedis.xdelex(STREAM_KEY_1, id1, nonExistentId); + List results = jedis.xdelex(STREAM_KEY_1, id1, nonExistentId); assertThat(results, hasSize(2)); - assertEquals(StreamTrimResult.DELETED, results.get(0)); // Existing entry - assertEquals(StreamTrimResult.NOT_FOUND, results.get(1)); // Non-existent entry + assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); // Existing entry + assertEquals(StreamEntryDeletionResult.NOT_FOUND, results.get(1)); // Non-existent entry // Verify existing entry is deleted assertEquals(0L, jedis.xlen(STREAM_KEY_1)); @@ -454,10 +454,10 @@ public void testXdelexWithConsumerGroups() { jedis.xack(STREAM_KEY_1, GROUP_NAME, readId1); // Test XDELEX with ACKNOWLEDGED mode - should only delete acknowledged entries - List results = jedis.xdelex(STREAM_KEY_1, StreamTrimMode.ACKNOWLEDGED, readId1, readId2); + List results = jedis.xdelex(STREAM_KEY_1, StreamDeletionPolicy.ACKNOWLEDGED, readId1, readId2); assertThat(results, hasSize(2)); - assertEquals(StreamTrimResult.DELETED, results.get(0)); // id1 was acknowledged - assertEquals(StreamTrimResult.ACKNOWLEDGED_NOT_DELETED, results.get(1)); // id2 not acknowledged + assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); // id1 was acknowledged + assertEquals(StreamEntryDeletionResult.ACKNOWLEDGED_NOT_DELETED, results.get(1)); // id2 not acknowledged // Verify only acknowledged entry was deleted assertEquals(1L, jedis.xlen(STREAM_KEY_1)); @@ -468,9 +468,9 @@ public void testXdelexWithConsumerGroups() { public void testXdelexEmptyStream() { // Test XDELEX on empty stream byte[] nonExistentId = "1-0".getBytes(); - List results = jedis.xdelex(STREAM_KEY_1, nonExistentId); + List results = jedis.xdelex(STREAM_KEY_1, nonExistentId); assertThat(results, hasSize(1)); - assertEquals(StreamTrimResult.NOT_FOUND, results.get(0)); + assertEquals(StreamEntryDeletionResult.NOT_FOUND, results.get(0)); } // ========== XTRIM Command Tests with trimmingMode ========== @@ -489,7 +489,8 @@ public void testXtrimWithKeepReferences() { jedis.xreadGroupBinary(GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3), streams); // Test XTRIM with KEEP_REFERENCES mode - should preserve PEL references - long trimmed = jedis.xtrim(STREAM_KEY_1, XTrimParams.xTrimParams().maxLen(3).trimmingMode(StreamTrimMode.KEEP_REFERENCES)); + long trimmed = jedis.xtrim(STREAM_KEY_1, XTrimParams.xTrimParams().maxLen(3).trimmingMode( + StreamDeletionPolicy.KEEP_REFERENCES)); assertEquals(2L, trimmed); // Should trim 2 entries assertEquals(3L, jedis.xlen(STREAM_KEY_1)); } @@ -517,7 +518,8 @@ public void testXtrimWithAcknowledged() { jedis.xack(STREAM_KEY_1, GROUP_NAME, readId1, readId2); // Test XTRIM with ACKNOWLEDGED mode - should only trim acknowledged entries - long trimmed = jedis.xtrim(STREAM_KEY_1, XTrimParams.xTrimParams().maxLen(3).trimmingMode(StreamTrimMode.ACKNOWLEDGED)); + long trimmed = jedis.xtrim(STREAM_KEY_1, XTrimParams.xTrimParams().maxLen(3).trimmingMode( + StreamDeletionPolicy.ACKNOWLEDGED)); // The exact behavior depends on implementation, but it should respect acknowledgment status assertTrue(trimmed >= 0); assertTrue(jedis.xlen(STREAM_KEY_1) <= 5); // Should not exceed original length diff --git a/src/test/java/redis/clients/jedis/commands/jedis/StreamsCommandsTest.java b/src/test/java/redis/clients/jedis/commands/jedis/StreamsCommandsTest.java index c254913499..72322e6988 100644 --- a/src/test/java/redis/clients/jedis/commands/jedis/StreamsCommandsTest.java +++ b/src/test/java/redis/clients/jedis/commands/jedis/StreamsCommandsTest.java @@ -36,7 +36,7 @@ import redis.clients.jedis.exceptions.JedisException; import redis.clients.jedis.params.*; import redis.clients.jedis.resps.*; -import redis.clients.jedis.args.StreamTrimMode; +import redis.clients.jedis.args.StreamDeletionPolicy; import redis.clients.jedis.util.RedisVersionUtil; import redis.clients.jedis.util.SafeEncoder; @@ -225,7 +225,7 @@ public void xaddWithTrimmingModeKeepReferences() { StreamEntryID newId = jedis.xadd(streamKey, XAddParams.xAddParams() .id(new StreamEntryID("6-0")) .maxLen(3) - .trimmingMode(StreamTrimMode.KEEP_REFERENCES), map); + .trimmingMode(StreamDeletionPolicy.KEEP_REFERENCES), map); assertNotNull(newId); // Stream should be trimmed to 3 entries @@ -263,7 +263,7 @@ public void xaddWithTrimmingModeDeleteReferences() { StreamEntryID newId = jedis.xadd(streamKey, XAddParams.xAddParams() .id(new StreamEntryID("6-0")) .maxLen(3) - .trimmingMode(StreamTrimMode.DELETE_REFERENCES), map); + .trimmingMode(StreamDeletionPolicy.DELETE_REFERENCES), map); assertNotNull(newId); // Stream should be trimmed to 3 entries @@ -308,7 +308,7 @@ public void xaddWithTrimmingModeAcknowledged() { StreamEntryID newId = jedis.xadd(streamKey, XAddParams.xAddParams() .id(new StreamEntryID("6-0")) .maxLen(3) - .trimmingMode(StreamTrimMode.ACKNOWLEDGED), map); + .trimmingMode(StreamDeletionPolicy.ACKNOWLEDGED), map); assertNotNull(newId); // Stream length should respect acknowledgment status @@ -347,7 +347,7 @@ public void xaddWithMinIdTrimmingModeKeepReferences() { StreamEntryID newId = jedis.xadd(streamKey, XAddParams.xAddParams() .id(new StreamEntryID("0-6")) .minId("0-3") - .trimmingMode(StreamTrimMode.KEEP_REFERENCES), map); + .trimmingMode(StreamDeletionPolicy.KEEP_REFERENCES), map); assertNotNull(newId); // Stream should have entries >= 0-3 plus the new entry @@ -386,7 +386,7 @@ public void xaddWithMinIdTrimmingModeDeleteReferences() { StreamEntryID newId = jedis.xadd(streamKey, XAddParams.xAddParams() .id(new StreamEntryID("0-6")) .minId("0-3") - .trimmingMode(StreamTrimMode.DELETE_REFERENCES), map); + .trimmingMode(StreamDeletionPolicy.DELETE_REFERENCES), map); assertNotNull(newId); // Stream should have entries >= 0-3 plus the new entry @@ -423,7 +423,7 @@ public void xaddWithApproximateTrimmingAndTrimmingMode() { .id(new StreamEntryID("11-0")) .maxLen(5) .approximateTrimming() - .trimmingMode(StreamTrimMode.KEEP_REFERENCES), map); + .trimmingMode(StreamDeletionPolicy.KEEP_REFERENCES), map); assertNotNull(newId); // With approximate trimming, the exact length may vary but should be around the target @@ -459,7 +459,7 @@ public void xaddWithExactTrimmingAndTrimmingMode() { .id(new StreamEntryID("6-0")) .maxLen(3) .exactTrimming() - .trimmingMode(StreamTrimMode.DELETE_REFERENCES), map); + .trimmingMode(StreamDeletionPolicy.DELETE_REFERENCES), map); assertNotNull(newId); // With exact trimming, stream should be exactly 3 entries @@ -496,7 +496,7 @@ public void xaddWithLimitAndTrimmingMode() { .maxLen(5) .approximateTrimming() // Required for limit to work .limit(2) // Limit the number of entries to examine for trimming - .trimmingMode(StreamTrimMode.KEEP_REFERENCES), map); + .trimmingMode(StreamDeletionPolicy.KEEP_REFERENCES), map); assertNotNull(newId); // With limit, trimming may be less aggressive diff --git a/src/test/java/redis/clients/jedis/commands/unified/StreamsBinaryCommandsTestBase.java b/src/test/java/redis/clients/jedis/commands/unified/StreamsBinaryCommandsTestBase.java index 0b0cdbcce8..2903a70c3b 100644 --- a/src/test/java/redis/clients/jedis/commands/unified/StreamsBinaryCommandsTestBase.java +++ b/src/test/java/redis/clients/jedis/commands/unified/StreamsBinaryCommandsTestBase.java @@ -5,14 +5,14 @@ import org.junit.jupiter.api.Test; import redis.clients.jedis.RedisProtocol; import redis.clients.jedis.StreamEntryID; -import redis.clients.jedis.args.StreamTrimMode; +import redis.clients.jedis.args.StreamDeletionPolicy; import redis.clients.jedis.exceptions.JedisDataException; import redis.clients.jedis.params.XAddParams; import redis.clients.jedis.params.XReadGroupParams; import redis.clients.jedis.params.XReadParams; import redis.clients.jedis.params.XTrimParams; import redis.clients.jedis.resps.StreamEntryBinary; -import redis.clients.jedis.resps.StreamTrimResult; +import redis.clients.jedis.resps.StreamEntryDeletionResult; import java.util.ArrayList; import java.util.HashMap; @@ -281,9 +281,9 @@ public void testXackdel() { byte[] readMessageId = messages.get(0).getValue().get(0).getID().toString().getBytes(); // Test XACKDEL - should acknowledge and delete the message - List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, readMessageId); + List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, readMessageId); assertThat(results, hasSize(1)); - assertEquals(StreamTrimResult.DELETED, results.get(0)); + assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); // Verify message is deleted from stream assertEquals(0L, jedis.xlen(STREAM_KEY_1)); @@ -309,9 +309,9 @@ public void testXackdelWithTrimMode() { // Test XACKDEL with KEEP_REFERENCES mode byte[] readId1 = messages.get(0).getValue().get(0).getID().toString().getBytes(); - List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, StreamTrimMode.KEEP_REFERENCES, readId1); + List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, StreamDeletionPolicy.KEEP_REFERENCES, readId1); assertThat(results, hasSize(1)); - assertEquals(StreamTrimResult.DELETED, results.get(0)); + assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); // Verify one message is deleted assertEquals(1L, jedis.xlen(STREAM_KEY_1)); @@ -326,11 +326,11 @@ public void testXackdelUnreadMessages() { byte[] id1 = jedis.xadd(STREAM_KEY_1, new XAddParams().id("1-0"), HASH_1); // Test XACKDEL on unread messages - should return NOT_FOUND for PEL - List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, id1); + List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, id1); assertThat(results, hasSize(1)); // Should return NOT_FOUND because message was never read by the consumer group - assertEquals(StreamTrimResult.NOT_FOUND, results.get(0)); + assertEquals(StreamEntryDeletionResult.NOT_FOUND, results.get(0)); // Stream should still contain the message assertEquals(1L, jedis.xlen(STREAM_KEY_1)); @@ -358,10 +358,10 @@ public void testXackdelMultipleMessages() { // Test XACKDEL with multiple IDs byte[] readId1 = messages.get(0).getValue().get(0).getID().toString().getBytes(); byte[] readId2 = messages.get(0).getValue().get(1).getID().toString().getBytes(); - List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, readId1, readId2); + List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, readId1, readId2); assertThat(results, hasSize(2)); - assertEquals(StreamTrimResult.DELETED, results.get(0)); - assertEquals(StreamTrimResult.DELETED, results.get(1)); + assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); + assertEquals(StreamEntryDeletionResult.DELETED, results.get(1)); // Verify two messages are deleted assertEquals(1L, jedis.xlen(STREAM_KEY_1)); @@ -380,9 +380,9 @@ public void testXdelex() { assertEquals(2L, jedis.xlen(STREAM_KEY_1)); // Test basic XDELEX without parameters (should behave like XDEL with KEEP_REFERENCES) - List results = jedis.xdelex(STREAM_KEY_1, id1); + List results = jedis.xdelex(STREAM_KEY_1, id1); assertThat(results, hasSize(1)); - assertEquals(StreamTrimResult.DELETED, results.get(0)); + assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); // Verify entry is deleted from stream assertEquals(1L, jedis.xlen(STREAM_KEY_1)); @@ -398,9 +398,9 @@ public void testXdelexWithTrimMode() { jedis.xadd(STREAM_KEY_1, new XAddParams().id("2-0"), HASH_2); // Test XDELEX with DELETE_REFERENCES mode - List results = jedis.xdelex(STREAM_KEY_1, StreamTrimMode.DELETE_REFERENCES, id1); + List results = jedis.xdelex(STREAM_KEY_1, StreamDeletionPolicy.DELETE_REFERENCES, id1); assertThat(results, hasSize(1)); - assertEquals(StreamTrimResult.DELETED, results.get(0)); + assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); // Verify entry is deleted from stream assertEquals(1L, jedis.xlen(STREAM_KEY_1)); @@ -418,10 +418,10 @@ public void testXdelexMultipleEntries() { assertEquals(3L, jedis.xlen(STREAM_KEY_1)); // Test XDELEX with multiple IDs - List results = jedis.xdelex(STREAM_KEY_1, id1, id3); + List results = jedis.xdelex(STREAM_KEY_1, id1, id3); assertThat(results, hasSize(2)); - assertEquals(StreamTrimResult.DELETED, results.get(0)); - assertEquals(StreamTrimResult.DELETED, results.get(1)); + assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); + assertEquals(StreamEntryDeletionResult.DELETED, results.get(1)); // Verify two entries are deleted assertEquals(1L, jedis.xlen(STREAM_KEY_1)); @@ -438,10 +438,10 @@ public void testXdelexNonExistentEntries() { // Test XDELEX with mix of existing and non-existent IDs byte[] nonExistentId = "999-0".getBytes(); - List results = jedis.xdelex(STREAM_KEY_1, id1, nonExistentId); + List results = jedis.xdelex(STREAM_KEY_1, id1, nonExistentId); assertThat(results, hasSize(2)); - assertEquals(StreamTrimResult.DELETED, results.get(0)); // Existing entry - assertEquals(StreamTrimResult.NOT_FOUND, results.get(1)); // Non-existent entry + assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); // Existing entry + assertEquals(StreamEntryDeletionResult.NOT_FOUND, results.get(1)); // Non-existent entry // Verify existing entry is deleted assertEquals(0L, jedis.xlen(STREAM_KEY_1)); @@ -471,10 +471,10 @@ public void testXdelexWithConsumerGroups() { jedis.xack(STREAM_KEY_1, GROUP_NAME, readId1); // Test XDELEX with ACKNOWLEDGED mode - should only delete acknowledged entries - List results = jedis.xdelex(STREAM_KEY_1, StreamTrimMode.ACKNOWLEDGED, readId1, readId2); + List results = jedis.xdelex(STREAM_KEY_1, StreamDeletionPolicy.ACKNOWLEDGED, readId1, readId2); assertThat(results, hasSize(2)); - assertEquals(StreamTrimResult.DELETED, results.get(0)); // id1 was acknowledged - assertEquals(StreamTrimResult.ACKNOWLEDGED_NOT_DELETED, results.get(1)); // id2 not acknowledged + assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); // id1 was acknowledged + assertEquals(StreamEntryDeletionResult.ACKNOWLEDGED_NOT_DELETED, results.get(1)); // id2 not acknowledged // Verify only acknowledged entry was deleted assertEquals(1L, jedis.xlen(STREAM_KEY_1)); @@ -487,9 +487,9 @@ public void testXdelexEmptyStream() { // Test XDELEX on empty stream byte[] nonExistentId = "1-0".getBytes(); - List results = jedis.xdelex(STREAM_KEY_1, nonExistentId); + List results = jedis.xdelex(STREAM_KEY_1, nonExistentId); assertThat(results, hasSize(1)); - assertEquals(StreamTrimResult.NOT_FOUND, results.get(0)); + assertEquals(StreamEntryDeletionResult.NOT_FOUND, results.get(0)); } // ========== XTRIM Command Tests with trimmingMode ========== @@ -510,7 +510,8 @@ public void testXtrimWithKeepReferences() { jedis.xreadGroupBinary(GROUP_NAME, CONSUMER_NAME, XReadGroupParams.xReadGroupParams().count(3), streams); // Test XTRIM with KEEP_REFERENCES mode - should preserve PEL references - long trimmed = jedis.xtrim(STREAM_KEY_1, XTrimParams.xTrimParams().maxLen(3).trimmingMode(StreamTrimMode.KEEP_REFERENCES)); + long trimmed = jedis.xtrim(STREAM_KEY_1, XTrimParams.xTrimParams().maxLen(3).trimmingMode( + StreamDeletionPolicy.KEEP_REFERENCES)); assertEquals(2L, trimmed); // Should trim 2 entries assertEquals(3L, jedis.xlen(STREAM_KEY_1)); } @@ -540,7 +541,8 @@ public void testXtrimWithAcknowledged() { jedis.xack(STREAM_KEY_1, GROUP_NAME, readId1, readId2); // Test XTRIM with ACKNOWLEDGED mode - should only trim acknowledged entries - long trimmed = jedis.xtrim(STREAM_KEY_1, XTrimParams.xTrimParams().maxLen(3).trimmingMode(StreamTrimMode.ACKNOWLEDGED)); + long trimmed = jedis.xtrim(STREAM_KEY_1, XTrimParams.xTrimParams().maxLen(3).trimmingMode( + StreamDeletionPolicy.ACKNOWLEDGED)); // The exact behavior depends on implementation, but it should respect acknowledgment status assertTrue(trimmed >= 0); assertTrue(jedis.xlen(STREAM_KEY_1) <= 5); // Should not exceed original length diff --git a/src/test/java/redis/clients/jedis/commands/unified/StreamsCommandsTestBase.java b/src/test/java/redis/clients/jedis/commands/unified/StreamsCommandsTestBase.java index a0f7981a5b..cdd1e2f99d 100644 --- a/src/test/java/redis/clients/jedis/commands/unified/StreamsCommandsTestBase.java +++ b/src/test/java/redis/clients/jedis/commands/unified/StreamsCommandsTestBase.java @@ -7,7 +7,7 @@ import org.junit.jupiter.params.provider.CsvSource; import redis.clients.jedis.RedisProtocol; import redis.clients.jedis.StreamEntryID; -import redis.clients.jedis.args.StreamTrimMode; +import redis.clients.jedis.args.StreamDeletionPolicy; import redis.clients.jedis.exceptions.JedisDataException; import redis.clients.jedis.params.XAddParams; import redis.clients.jedis.params.XPendingParams; @@ -15,7 +15,7 @@ import redis.clients.jedis.params.XTrimParams; import redis.clients.jedis.resps.StreamEntry; import redis.clients.jedis.resps.StreamPendingEntry; -import redis.clients.jedis.resps.StreamTrimResult; +import redis.clients.jedis.resps.StreamEntryDeletionResult; import java.util.HashMap; import java.util.List; @@ -166,7 +166,7 @@ public void xaddErrorCases() { @ParameterizedTest @CsvSource({ "KEEP_REFERENCES,3", "DELETE_REFERENCES,0" }) @SinceRedisVersion("8.1.240") - public void xaddWithTrimmingMode(StreamTrimMode trimMode, int expected) { + public void xaddWithTrimmingMode(StreamDeletionPolicy trimMode, int expected) { setUpTestStream(); Map map = singletonMap("field", "value"); @@ -224,7 +224,7 @@ public void xaddWithTrimmingModeAcknowledged() { // Add new entry with maxLen=3 and ACKNOWLEDGED mode StreamEntryID newId = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams() - .id(new StreamEntryID("6-0")).maxLen(3).trimmingMode(StreamTrimMode.ACKNOWLEDGED), + .id(new StreamEntryID("6-0")).maxLen(3).trimmingMode(StreamDeletionPolicy.ACKNOWLEDGED), map); assertNotNull(newId); @@ -288,7 +288,7 @@ public void xtrimApproximate() { @ParameterizedTest @CsvSource({ "KEEP_REFERENCES,3", "DELETE_REFERENCES,1" }) @SinceRedisVersion("8.1.240") - public void xaddWithMinIdTrimmingMode(StreamTrimMode trimMode, int expected) { + public void xaddWithMinIdTrimmingMode(StreamDeletionPolicy trimMode, int expected) { setUpTestStream(); Map map = singletonMap("field", "value"); @@ -340,7 +340,7 @@ public void xaddWithApproximateTrimmingAndTrimmingMode() { // Add new entry with approximate trimming and KEEP_REFERENCES mode StreamEntryID newId = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().id(new StreamEntryID("11-0")).maxLen(5).approximateTrimming() - .trimmingMode(StreamTrimMode.KEEP_REFERENCES), + .trimmingMode(StreamDeletionPolicy.KEEP_REFERENCES), map); assertNotNull(newId); @@ -372,7 +372,7 @@ public void xaddWithExactTrimmingAndTrimmingMode() { // Add new entry with exact trimming and DELETE_REFERENCES mode StreamEntryID newId = jedis.xadd(STREAM_KEY_1, XAddParams.xAddParams().id(new StreamEntryID("6-0")).maxLen(3).exactTrimming() - .trimmingMode(StreamTrimMode.DELETE_REFERENCES), + .trimmingMode(StreamDeletionPolicy.DELETE_REFERENCES), map); assertNotNull(newId); @@ -408,7 +408,7 @@ public void xaddWithLimitAndTrimmingMode() { // to // work .limit(2) // Limit the number of entries to examine for trimming - .trimmingMode(StreamTrimMode.KEEP_REFERENCES), + .trimmingMode(StreamDeletionPolicy.KEEP_REFERENCES), map); assertNotNull(newId); @@ -562,9 +562,9 @@ public void xackdelBasic() { StreamEntryID readMessageId = messages.get(0).getValue().get(0).getID(); // Test XACKDEL - should acknowledge and delete the message - List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, readMessageId); + List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, readMessageId); assertThat(results, hasSize(1)); - assertEquals(StreamTrimResult.DELETED, results.get(0)); + assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); // Verify message is deleted from stream assertEquals(0L, jedis.xlen(STREAM_KEY_1)); @@ -590,10 +590,10 @@ public void xackdelWithTrimMode() { // Test XACKDEL with KEEP_REFERENCES mode StreamEntryID readId1 = messages.get(0).getValue().get(0).getID(); - List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, - StreamTrimMode.KEEP_REFERENCES, readId1); + List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, + StreamDeletionPolicy.KEEP_REFERENCES, readId1); assertThat(results, hasSize(1)); - assertEquals(StreamTrimResult.DELETED, results.get(0)); + assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); // Verify one message is deleted assertEquals(1L, jedis.xlen(STREAM_KEY_1)); @@ -608,11 +608,11 @@ public void xackdelUnreadMessages() { StreamEntryID id1 = jedis.xadd(STREAM_KEY_1, new StreamEntryID("1-0"), HASH_1); // Test XACKDEL on unread messages - should return NOT_FOUND for PEL - List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, id1); + List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, id1); assertThat(results, hasSize(1)); // Should return NOT_FOUND because message was never read by the consumer group - assertEquals(StreamTrimResult.NOT_FOUND, results.get(0)); + assertEquals(StreamEntryDeletionResult.NOT_FOUND, results.get(0)); // Stream should still contain the message assertEquals(1L, jedis.xlen(STREAM_KEY_1)); @@ -640,10 +640,10 @@ public void xackdelMultipleMessages() { // Test XACKDEL with multiple IDs StreamEntryID readId1 = messages.get(0).getValue().get(0).getID(); StreamEntryID readId2 = messages.get(0).getValue().get(1).getID(); - List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, readId1, readId2); + List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, readId1, readId2); assertThat(results, hasSize(2)); - assertEquals(StreamTrimResult.DELETED, results.get(0)); - assertEquals(StreamTrimResult.DELETED, results.get(1)); + assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); + assertEquals(StreamEntryDeletionResult.DELETED, results.get(1)); // Verify two messages are deleted assertEquals(1L, jedis.xlen(STREAM_KEY_1)); @@ -662,9 +662,9 @@ public void xdelexBasic() { assertEquals(2L, jedis.xlen(STREAM_KEY_1)); // Test basic XDELEX without parameters (should behave like XDEL with KEEP_REFERENCES) - List results = jedis.xdelex(STREAM_KEY_1, id1); + List results = jedis.xdelex(STREAM_KEY_1, id1); assertThat(results, hasSize(1)); - assertEquals(StreamTrimResult.DELETED, results.get(0)); + assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); // Verify entry is deleted from stream assertEquals(1L, jedis.xlen(STREAM_KEY_1)); @@ -680,10 +680,10 @@ public void xdelexWithTrimMode() { StreamEntryID id2 = jedis.xadd(STREAM_KEY_1, new StreamEntryID("2-0"), HASH_2); // Test XDELEX with DELETE_REFERENCES mode - List results = jedis.xdelex(STREAM_KEY_1, StreamTrimMode.DELETE_REFERENCES, + List results = jedis.xdelex(STREAM_KEY_1, StreamDeletionPolicy.DELETE_REFERENCES, id1); assertThat(results, hasSize(1)); - assertEquals(StreamTrimResult.DELETED, results.get(0)); + assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); // Verify entry is deleted from stream assertEquals(1L, jedis.xlen(STREAM_KEY_1)); @@ -701,10 +701,10 @@ public void xdelexMultipleEntries() { assertEquals(3L, jedis.xlen(STREAM_KEY_1)); // Test XDELEX with multiple IDs - List results = jedis.xdelex(STREAM_KEY_1, id1, id3); + List results = jedis.xdelex(STREAM_KEY_1, id1, id3); assertThat(results, hasSize(2)); - assertEquals(StreamTrimResult.DELETED, results.get(0)); - assertEquals(StreamTrimResult.DELETED, results.get(1)); + assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); + assertEquals(StreamEntryDeletionResult.DELETED, results.get(1)); // Verify two entries are deleted assertEquals(1L, jedis.xlen(STREAM_KEY_1)); @@ -721,10 +721,10 @@ public void xdelexNonExistentEntries() { // Test XDELEX with mix of existing and non-existent IDs StreamEntryID nonExistentId = new StreamEntryID("999-0"); - List results = jedis.xdelex(STREAM_KEY_1, id1, nonExistentId); + List results = jedis.xdelex(STREAM_KEY_1, id1, nonExistentId); assertThat(results, hasSize(2)); - assertEquals(StreamTrimResult.DELETED, results.get(0)); // Existing entry - assertEquals(StreamTrimResult.NOT_FOUND, results.get(1)); // Non-existent entry + assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); // Existing entry + assertEquals(StreamEntryDeletionResult.NOT_FOUND, results.get(1)); // Non-existent entry // Verify existing entry is deleted assertEquals(0L, jedis.xlen(STREAM_KEY_1)); @@ -754,11 +754,11 @@ public void xdelexWithConsumerGroups() { jedis.xack(STREAM_KEY_1, GROUP_NAME, readId1); // Test XDELEX with ACKNOWLEDGED mode - should only delete acknowledged entries - List results = jedis.xdelex(STREAM_KEY_1, StreamTrimMode.ACKNOWLEDGED, + List results = jedis.xdelex(STREAM_KEY_1, StreamDeletionPolicy.ACKNOWLEDGED, readId1, readId2); assertThat(results, hasSize(2)); - assertEquals(StreamTrimResult.DELETED, results.get(0)); // id1 was acknowledged - assertEquals(StreamTrimResult.ACKNOWLEDGED_NOT_DELETED, results.get(1)); // id2 not acknowledged + assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); // id1 was acknowledged + assertEquals(StreamEntryDeletionResult.ACKNOWLEDGED_NOT_DELETED, results.get(1)); // id2 not acknowledged // Verify only acknowledged entry was deleted assertEquals(1L, jedis.xlen(STREAM_KEY_1)); @@ -771,8 +771,8 @@ public void xdelexEmptyStream() { // Test XDELEX on empty stream StreamEntryID nonExistentId = new StreamEntryID("1-0"); - List results = jedis.xdelex(STREAM_KEY_1, nonExistentId); + List results = jedis.xdelex(STREAM_KEY_1, nonExistentId); assertThat(results, hasSize(1)); - assertEquals(StreamTrimResult.NOT_FOUND, results.get(0)); + assertEquals(StreamEntryDeletionResult.NOT_FOUND, results.get(0)); } } diff --git a/src/test/java/redis/clients/jedis/resps/StreamEntryDeletionResultTest.java b/src/test/java/redis/clients/jedis/resps/StreamEntryDeletionResultTest.java index ff49f7cdd0..2b125cd9af 100644 --- a/src/test/java/redis/clients/jedis/resps/StreamEntryDeletionResultTest.java +++ b/src/test/java/redis/clients/jedis/resps/StreamEntryDeletionResultTest.java @@ -8,41 +8,41 @@ public class StreamEntryDeletionResultTest { @Test public void testFromCode() { - assertEquals(StreamTrimResult.NOT_FOUND, StreamTrimResult.fromCode(-1)); - assertEquals(StreamTrimResult.DELETED, StreamTrimResult.fromCode(1)); - assertEquals(StreamTrimResult.ACKNOWLEDGED_NOT_DELETED, StreamTrimResult.fromCode(2)); + assertEquals(StreamEntryDeletionResult.NOT_FOUND, StreamEntryDeletionResult.fromCode(-1)); + assertEquals(StreamEntryDeletionResult.DELETED, StreamEntryDeletionResult.fromCode(1)); + assertEquals(StreamEntryDeletionResult.ACKNOWLEDGED_NOT_DELETED, StreamEntryDeletionResult.fromCode(2)); } @Test public void testFromCodeInvalid() { - assertThrows(IllegalArgumentException.class, () -> StreamTrimResult.fromCode(0)); - assertThrows(IllegalArgumentException.class, () -> StreamTrimResult.fromCode(3)); - assertThrows(IllegalArgumentException.class, () -> StreamTrimResult.fromCode(-2)); + assertThrows(IllegalArgumentException.class, () -> StreamEntryDeletionResult.fromCode(0)); + assertThrows(IllegalArgumentException.class, () -> StreamEntryDeletionResult.fromCode(3)); + assertThrows(IllegalArgumentException.class, () -> StreamEntryDeletionResult.fromCode(-2)); } @Test public void testFromLong() { - assertEquals(StreamTrimResult.NOT_FOUND, StreamTrimResult.fromLong(-1L)); - assertEquals(StreamTrimResult.DELETED, StreamTrimResult.fromLong(1L)); - assertEquals(StreamTrimResult.ACKNOWLEDGED_NOT_DELETED, StreamTrimResult.fromLong(2L)); + assertEquals(StreamEntryDeletionResult.NOT_FOUND, StreamEntryDeletionResult.fromLong(-1L)); + assertEquals(StreamEntryDeletionResult.DELETED, StreamEntryDeletionResult.fromLong(1L)); + assertEquals(StreamEntryDeletionResult.ACKNOWLEDGED_NOT_DELETED, StreamEntryDeletionResult.fromLong(2L)); } @Test public void testFromLongNull() { - assertThrows(IllegalArgumentException.class, () -> StreamTrimResult.fromLong(null)); + assertThrows(IllegalArgumentException.class, () -> StreamEntryDeletionResult.fromLong(null)); } @Test public void testGetCode() { - assertEquals(-1, StreamTrimResult.NOT_FOUND.getCode()); - assertEquals(1, StreamTrimResult.DELETED.getCode()); - assertEquals(2, StreamTrimResult.ACKNOWLEDGED_NOT_DELETED.getCode()); + assertEquals(-1, StreamEntryDeletionResult.NOT_FOUND.getCode()); + assertEquals(1, StreamEntryDeletionResult.DELETED.getCode()); + assertEquals(2, StreamEntryDeletionResult.ACKNOWLEDGED_NOT_DELETED.getCode()); } @Test public void testToString() { - assertEquals("NOT_FOUND(-1)", StreamTrimResult.NOT_FOUND.toString()); - assertEquals("DELETED(1)", StreamTrimResult.DELETED.toString()); - assertEquals("ACKNOWLEDGED_NOT_DELETED(2)", StreamTrimResult.ACKNOWLEDGED_NOT_DELETED.toString()); + assertEquals("NOT_FOUND(-1)", StreamEntryDeletionResult.NOT_FOUND.toString()); + assertEquals("DELETED(1)", StreamEntryDeletionResult.DELETED.toString()); + assertEquals("ACKNOWLEDGED_NOT_DELETED(2)", StreamEntryDeletionResult.ACKNOWLEDGED_NOT_DELETED.toString()); } } From 07cd2a6c63a8e48de40aa6684b0d56b50bef84ac Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 25 Jul 2025 12:49:28 +0200 Subject: [PATCH 4/5] Fix formatting in new files --- .../jedis/args/StreamDeletionPolicy.java | 12 +++--- .../resps/StreamEntryDeletionResult.java | 41 +++++++++---------- .../unified/StreamsCommandsTestBase.java | 17 ++++---- .../resps/StreamEntryDeletionResultTest.java | 9 ++-- 4 files changed, 41 insertions(+), 38 deletions(-) diff --git a/src/main/java/redis/clients/jedis/args/StreamDeletionPolicy.java b/src/main/java/redis/clients/jedis/args/StreamDeletionPolicy.java index bd065346fa..640c71dea9 100644 --- a/src/main/java/redis/clients/jedis/args/StreamDeletionPolicy.java +++ b/src/main/java/redis/clients/jedis/args/StreamDeletionPolicy.java @@ -3,20 +3,20 @@ import redis.clients.jedis.util.SafeEncoder; /** - * Deletion policy for stream commands that handle consumer group references. - * Used with XDELEX, XACKDEL, and enhanced XADD/XTRIM commands. + * Deletion policy for stream commands that handle consumer group references. Used with XDELEX, + * XACKDEL, and enhanced XADD/XTRIM commands. */ public enum StreamDeletionPolicy implements Rawable { /** - * Preserves existing references to entries in all consumer groups' PEL. - * This is the default behavior similar to XDEL. + * Preserves existing references to entries in all consumer groups' PEL. This is the default + * behavior similar to XDEL. */ KEEP_REFERENCES("KEEPREF"), /** - * Removes all references to entries from all consumer groups' pending entry lists, - * effectively cleaning up all traces of the messages. + * Removes all references to entries from all consumer groups' pending entry lists, effectively + * cleaning up all traces of the messages. */ DELETE_REFERENCES("DELREF"), diff --git a/src/main/java/redis/clients/jedis/resps/StreamEntryDeletionResult.java b/src/main/java/redis/clients/jedis/resps/StreamEntryDeletionResult.java index bf48cd6349..7d2cd53897 100644 --- a/src/main/java/redis/clients/jedis/resps/StreamEntryDeletionResult.java +++ b/src/main/java/redis/clients/jedis/resps/StreamEntryDeletionResult.java @@ -1,49 +1,47 @@ package redis.clients.jedis.resps; /** - * Represents the result of a stream entry deletion operation for XDELEX and XACKDEL commands. - * - NOT_FOUND (-1): ID doesn't exist in stream - * - DELETED (1): Entry was deleted/acknowledged and deleted - * - ACKNOWLEDGED_NOT_DELETED (2): Entry was acknowledged but not deleted (still has dangling references) + * Represents the result of a stream entry deletion operation for XDELEX and XACKDEL commands. - + * NOT_FOUND (-1): ID doesn't exist in stream - DELETED (1): Entry was deleted/acknowledged and + * deleted - ACKNOWLEDGED_NOT_DELETED (2): Entry was acknowledged but not deleted (still has + * dangling references) */ public enum StreamEntryDeletionResult { - + /** - * The stream entry ID doesn't exist in the stream. - * Returned when trying to delete/acknowledge a non-existent entry. + * The stream entry ID doesn't exist in the stream. Returned when trying to delete/acknowledge a + * non-existent entry. */ NOT_FOUND(-1), - + /** - * The entry was successfully deleted/acknowledged and deleted. - * This is the typical successful case. + * The entry was successfully deleted/acknowledged and deleted. This is the typical successful + * case. */ DELETED(1), - + /** - * The entry was acknowledged but not deleted because it still has dangling references - * in other consumer groups' pending entry lists. + * The entry was acknowledged but not deleted because it still has dangling references in other + * consumer groups' pending entry lists. */ ACKNOWLEDGED_NOT_DELETED(2); - + private final int code; - + StreamEntryDeletionResult(int code) { this.code = code; } - + /** * Gets the numeric code returned by Redis for this result. - * * @return the numeric code (-1, 1, or 2) */ public int getCode() { return code; } - + /** * Creates a StreamEntryDeletionResult from the numeric code returned by Redis. - * * @param code the numeric code from Redis * @return the corresponding StreamEntryDeletionResult * @throws IllegalArgumentException if the code is not recognized @@ -60,10 +58,9 @@ public static StreamEntryDeletionResult fromCode(int code) { throw new IllegalArgumentException("Unknown stream entry deletion result code: " + code); } } - + /** * Creates a StreamEntryDeletionResult from a Long value returned by Redis. - * * @param value the Long value from Redis * @return the corresponding StreamEntryDeletionResult * @throws IllegalArgumentException if the value is null or not recognized @@ -74,7 +71,7 @@ public static StreamEntryDeletionResult fromLong(Long value) { } return fromCode(value.intValue()); } - + @Override public String toString() { return name() + "(" + code + ")"; diff --git a/src/test/java/redis/clients/jedis/commands/unified/StreamsCommandsTestBase.java b/src/test/java/redis/clients/jedis/commands/unified/StreamsCommandsTestBase.java index cdd1e2f99d..86d59be8af 100644 --- a/src/test/java/redis/clients/jedis/commands/unified/StreamsCommandsTestBase.java +++ b/src/test/java/redis/clients/jedis/commands/unified/StreamsCommandsTestBase.java @@ -562,7 +562,8 @@ public void xackdelBasic() { StreamEntryID readMessageId = messages.get(0).getValue().get(0).getID(); // Test XACKDEL - should acknowledge and delete the message - List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, readMessageId); + List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, + readMessageId); assertThat(results, hasSize(1)); assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); @@ -640,7 +641,8 @@ public void xackdelMultipleMessages() { // Test XACKDEL with multiple IDs StreamEntryID readId1 = messages.get(0).getValue().get(0).getID(); StreamEntryID readId2 = messages.get(0).getValue().get(1).getID(); - List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, readId1, readId2); + List results = jedis.xackdel(STREAM_KEY_1, GROUP_NAME, readId1, + readId2); assertThat(results, hasSize(2)); assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); assertEquals(StreamEntryDeletionResult.DELETED, results.get(1)); @@ -680,8 +682,8 @@ public void xdelexWithTrimMode() { StreamEntryID id2 = jedis.xadd(STREAM_KEY_1, new StreamEntryID("2-0"), HASH_2); // Test XDELEX with DELETE_REFERENCES mode - List results = jedis.xdelex(STREAM_KEY_1, StreamDeletionPolicy.DELETE_REFERENCES, - id1); + List results = jedis.xdelex(STREAM_KEY_1, + StreamDeletionPolicy.DELETE_REFERENCES, id1); assertThat(results, hasSize(1)); assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); @@ -754,11 +756,12 @@ public void xdelexWithConsumerGroups() { jedis.xack(STREAM_KEY_1, GROUP_NAME, readId1); // Test XDELEX with ACKNOWLEDGED mode - should only delete acknowledged entries - List results = jedis.xdelex(STREAM_KEY_1, StreamDeletionPolicy.ACKNOWLEDGED, - readId1, readId2); + List results = jedis.xdelex(STREAM_KEY_1, + StreamDeletionPolicy.ACKNOWLEDGED, readId1, readId2); assertThat(results, hasSize(2)); assertEquals(StreamEntryDeletionResult.DELETED, results.get(0)); // id1 was acknowledged - assertEquals(StreamEntryDeletionResult.ACKNOWLEDGED_NOT_DELETED, results.get(1)); // id2 not acknowledged + assertEquals(StreamEntryDeletionResult.ACKNOWLEDGED_NOT_DELETED, results.get(1)); // id2 not + // acknowledged // Verify only acknowledged entry was deleted assertEquals(1L, jedis.xlen(STREAM_KEY_1)); diff --git a/src/test/java/redis/clients/jedis/resps/StreamEntryDeletionResultTest.java b/src/test/java/redis/clients/jedis/resps/StreamEntryDeletionResultTest.java index 2b125cd9af..ea6a4c0b53 100644 --- a/src/test/java/redis/clients/jedis/resps/StreamEntryDeletionResultTest.java +++ b/src/test/java/redis/clients/jedis/resps/StreamEntryDeletionResultTest.java @@ -10,7 +10,8 @@ public class StreamEntryDeletionResultTest { public void testFromCode() { assertEquals(StreamEntryDeletionResult.NOT_FOUND, StreamEntryDeletionResult.fromCode(-1)); assertEquals(StreamEntryDeletionResult.DELETED, StreamEntryDeletionResult.fromCode(1)); - assertEquals(StreamEntryDeletionResult.ACKNOWLEDGED_NOT_DELETED, StreamEntryDeletionResult.fromCode(2)); + assertEquals(StreamEntryDeletionResult.ACKNOWLEDGED_NOT_DELETED, + StreamEntryDeletionResult.fromCode(2)); } @Test @@ -24,7 +25,8 @@ public void testFromCodeInvalid() { public void testFromLong() { assertEquals(StreamEntryDeletionResult.NOT_FOUND, StreamEntryDeletionResult.fromLong(-1L)); assertEquals(StreamEntryDeletionResult.DELETED, StreamEntryDeletionResult.fromLong(1L)); - assertEquals(StreamEntryDeletionResult.ACKNOWLEDGED_NOT_DELETED, StreamEntryDeletionResult.fromLong(2L)); + assertEquals(StreamEntryDeletionResult.ACKNOWLEDGED_NOT_DELETED, + StreamEntryDeletionResult.fromLong(2L)); } @Test @@ -43,6 +45,7 @@ public void testGetCode() { public void testToString() { assertEquals("NOT_FOUND(-1)", StreamEntryDeletionResult.NOT_FOUND.toString()); assertEquals("DELETED(1)", StreamEntryDeletionResult.DELETED.toString()); - assertEquals("ACKNOWLEDGED_NOT_DELETED(2)", StreamEntryDeletionResult.ACKNOWLEDGED_NOT_DELETED.toString()); + assertEquals("ACKNOWLEDGED_NOT_DELETED(2)", + StreamEntryDeletionResult.ACKNOWLEDGED_NOT_DELETED.toString()); } } From 34d980c289fb28f2580a2c2ae7153e67b729cdd0 Mon Sep 17 00:00:00 2001 From: ggivo Date: Fri, 25 Jul 2025 15:31:12 +0300 Subject: [PATCH 5/5] fix doc comments formatting --- pom.xml | 12 +++++++++- .../resps/StreamEntryDeletionResult.java | 23 ++++++++++++------- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 22ea1cdce7..7462072032 100644 --- a/pom.xml +++ b/pom.xml @@ -335,8 +335,18 @@ ${project.basedir}/hbase-formatter.xml - ${project.basedir}/src/main/java/redis/clients/jedis/annots + ${project.basedir} + + + src/main/java/redis/clients/jedis/annots/*.java + src/main/java/redis/clients/jedis/resps/StreamEntryDeletionResult.java + src/main/java/redis/clients/jedisargs/StreamDeletionPolicy.java + src/test/java/redis/clients/jedis/commands/StreamsCommandsTestBase.java + src/test/java/redis/clients/jedis/commands/jedis/ClusterStreamsCommandsTest.java + src/test/java/redis/clients/jedis/commands/jedis/PooledStreamsCommandsTest.java + src/test/java/redis/clients/jedis/resps/StreamEntryDeletionResultTest.java + diff --git a/src/main/java/redis/clients/jedis/resps/StreamEntryDeletionResult.java b/src/main/java/redis/clients/jedis/resps/StreamEntryDeletionResult.java index 7d2cd53897..974f8f3476 100644 --- a/src/main/java/redis/clients/jedis/resps/StreamEntryDeletionResult.java +++ b/src/main/java/redis/clients/jedis/resps/StreamEntryDeletionResult.java @@ -1,22 +1,29 @@ package redis.clients.jedis.resps; /** - * Represents the result of a stream entry deletion operation for XDELEX and XACKDEL commands. - - * NOT_FOUND (-1): ID doesn't exist in stream - DELETED (1): Entry was deleted/acknowledged and - * deleted - ACKNOWLEDGED_NOT_DELETED (2): Entry was acknowledged but not deleted (still has - * dangling references) + * Represents the result of a stream entry deletion operation for XDELEX and XACKDEL commands. + *
    + *
  • NOT_FOUND (-1): ID doesn't exist in stream
  • + *
  • DELETED (1): Entry was deleted/acknowledged and deleted
  • + *
  • ACKNOWLEDGED_NOT_DELETED (2): Entry was acknowledged but not deleted (still has dangling + * references)
  • + *
*/ public enum StreamEntryDeletionResult { /** - * The stream entry ID doesn't exist in the stream. Returned when trying to delete/acknowledge a - * non-existent entry. + * The stream entry ID doesn't exist in the stream. + *

+ * Returned when trying to delete/acknowledge a non-existent entry. + *

*/ NOT_FOUND(-1), /** - * The entry was successfully deleted/acknowledged and deleted. This is the typical successful - * case. + * The entry was successfully deleted/acknowledged and deleted. + *

+ * This is the typical successful case. + *

*/ DELETED(1),