From 8da9665f974d0443398ce2acbae123e7f1d61547 Mon Sep 17 00:00:00 2001 From: Andrew Carbonetto Date: Fri, 22 Mar 2024 09:41:21 -0700 Subject: [PATCH 1/7] Add Stream XADD command to Java Signed-off-by: Andrew Carbonetto --- .../src/main/java/glide/api/BaseClient.java | 22 ++- .../api/commands/StreamBaseCommands.java | 47 +++++ .../glide/api/models/BaseTransaction.java | 36 ++++ .../api/models/commands/StreamAddOptions.java | 144 ++++++++++++++ .../test/java/glide/api/RedisClientTest.java | 181 ++++++++++++++++++ .../glide/api/models/TransactionTests.java | 13 ++ .../test/java/glide/SharedCommandTests.java | 89 +++++++++ 7 files changed, 531 insertions(+), 1 deletion(-) create mode 100644 java/client/src/main/java/glide/api/commands/StreamBaseCommands.java create mode 100644 java/client/src/main/java/glide/api/models/commands/StreamAddOptions.java diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 784f63f474..9072eb9627 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -47,6 +47,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.TTL; import static redis_request.RedisRequestOuterClass.RequestType.Type; import static redis_request.RedisRequestOuterClass.RequestType.Unlink; +import static redis_request.RedisRequestOuterClass.RequestType.XAdd; import static redis_request.RedisRequestOuterClass.RequestType.ZScore; import static redis_request.RedisRequestOuterClass.RequestType.Zadd; import static redis_request.RedisRequestOuterClass.RequestType.Zcard; @@ -57,11 +58,13 @@ import glide.api.commands.ListBaseCommands; import glide.api.commands.SetBaseCommands; import glide.api.commands.SortedSetBaseCommands; +import glide.api.commands.StreamBaseCommands; import glide.api.commands.StringCommands; import glide.api.models.Script; import glide.api.models.commands.ExpireOptions; import glide.api.models.commands.ScriptOptions; import glide.api.models.commands.SetOptions; +import glide.api.models.commands.StreamAddOptions; import glide.api.models.commands.ZaddOptions; import glide.api.models.configuration.BaseClientConfiguration; import glide.api.models.exceptions.RedisException; @@ -75,6 +78,7 @@ import glide.managers.CommandManager; import glide.managers.ConnectionManager; import java.util.List; +import glide.utils.ArrayTransformUtils; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; @@ -95,7 +99,8 @@ public abstract class BaseClient HashBaseCommands, ListBaseCommands, SetBaseCommands, - SortedSetBaseCommands { + SortedSetBaseCommands, + StreamBaseCommands { /** Redis simple string response with "OK" */ public static final String OK = ConstantResponse.OK.toString(); @@ -619,6 +624,21 @@ public CompletableFuture zscore(@NonNull String key, @NonNull String mem ZScore, new String[] {key, member}, this::handleDoubleOrNullResponse); } + @Override + public CompletableFuture xadd(@NonNull String key, Map values) { + return xadd(key, values, StreamAddOptions.builder().build()); + } + + @Override + public CompletableFuture xadd( + @NonNull String key, Map values, StreamAddOptions options) { + String[] arguments = + ArrayUtils.addAll( + ArrayUtils.addFirst(options.toArgs(), key), + ArrayTransformUtils.convertMapToKeyValueStringArray(values)); + return commandManager.submitNewCommand(XAdd, arguments, this::handleStringOrNullResponse); + } + @Override public CompletableFuture pttl(@NonNull String key) { return commandManager.submitNewCommand(PTTL, new String[] {key}, this::handleLongResponse); diff --git a/java/client/src/main/java/glide/api/commands/StreamBaseCommands.java b/java/client/src/main/java/glide/api/commands/StreamBaseCommands.java new file mode 100644 index 0000000000..837d19ec85 --- /dev/null +++ b/java/client/src/main/java/glide/api/commands/StreamBaseCommands.java @@ -0,0 +1,47 @@ +/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ +package glide.api.commands; + +import glide.api.models.commands.StreamAddOptions; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +/** + * Supports commands and transactions for the "Stream Commands" group for standalone clients and + * cluster clients. + * + * @see Stream Commands + */ +public interface StreamBaseCommands { + + /** + * Adds an entry to the specified stream. + * + * @see redis.io for details. + * @param key The key of the stream. + * @param values field-value pairs to be added to the entry. + * @return The id of the added entry. + * @example + *
{@code
+     * String streamId = client.xadd("key", Map.of("name", "Sara", "surname", "OConnor").get();
+     * System.out.println("Stream: " + streamId);
+     * }
+ */ + CompletableFuture xadd(String key, Map values); + + /** + * Adds an entry to the specified stream. + * + * @see redis.io for details. + * @param key The key of the stream. + * @param values field-value pairs to be added to the entry. + * @param options options. + * @return The id of the added entry, or null if options.makeStream is + * set to false and no stream with the matching key exists. + * @example + *
{@code
+     * String streamId = client.xadd("key", Map.of("name", "Sara", "surname", "OConnor").get();
+     * System.out.println("Stream: " + streamId);
+     * }
+ */ + CompletableFuture xadd(String key, Map values, StreamAddOptions options); +} diff --git a/java/client/src/main/java/glide/api/models/BaseTransaction.java b/java/client/src/main/java/glide/api/models/BaseTransaction.java index a0cd91ea89..722b9d506b 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -57,6 +57,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.Type; import static redis_request.RedisRequestOuterClass.RequestType.Unlink; import static redis_request.RedisRequestOuterClass.RequestType.ZScore; +import static redis_request.RedisRequestOuterClass.RequestType.XAdd; import static redis_request.RedisRequestOuterClass.RequestType.Zadd; import static redis_request.RedisRequestOuterClass.RequestType.Zcard; import static redis_request.RedisRequestOuterClass.RequestType.Zrem; @@ -67,7 +68,9 @@ import glide.api.models.commands.SetOptions; import glide.api.models.commands.SetOptions.ConditionalSet; import glide.api.models.commands.SetOptions.SetOptionsBuilder; +import glide.api.models.commands.StreamAddOptions; import glide.api.models.commands.ZaddOptions; +import glide.utils.ArrayTransformUtils; import java.util.Map; import lombok.Getter; import lombok.NonNull; @@ -1292,6 +1295,39 @@ public T zscore(@NonNull String key, @NonNull String member) { return getThis(); } + /** + * Adds an entry to the specified stream. + * + * @see redis.io for details. + * @param key The key of the stream. + * @param values field-value pairs to be added to the entry. + * @return Command Response - The id of the added entry. + */ + public T xadd(String key, Map values) { + return this.xadd(key, values, StreamAddOptions.builder().build()); + } + + /** + * Adds an entry to the specified stream. + * + * @see redis.io for details. + * @param key The key of the stream. + * @param values field-value pairs to be added to the entry. + * @param options options. + * @return Command Response - The id of the added entry, or null if + * options.makeStream is set to false and no stream with the matching + * key exists. + */ + public T xadd(String key, Map values, StreamAddOptions options) { + String[] arguments = + ArrayUtils.addAll( + ArrayUtils.addFirst(options.toArgs(), key), + ArrayTransformUtils.convertMapToKeyValueStringArray(values)); + ArgsArray commandArgs = buildArgs(arguments); + protobufTransaction.addCommands(buildCommand(XAdd, commandArgs)); + return getThis(); + } + /** * Returns the remaining time to live of key that has a timeout, in milliseconds. * diff --git a/java/client/src/main/java/glide/api/models/commands/StreamAddOptions.java b/java/client/src/main/java/glide/api/models/commands/StreamAddOptions.java new file mode 100644 index 0000000000..e1f1b99153 --- /dev/null +++ b/java/client/src/main/java/glide/api/models/commands/StreamAddOptions.java @@ -0,0 +1,144 @@ +/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ +package glide.api.models.commands; + +import glide.api.commands.StreamBaseCommands; +import java.util.ArrayList; +import java.util.List; +import lombok.Builder; + +/** + * Optional arguments to {@link StreamBaseCommands#xadd} + * + * @see redis.io + */ +@Builder +public final class StreamAddOptions { + + public static final String NO_MAKE_STREAM_REDIS_API = "NOMKSTREAM"; + public static final String ID_WILDCARD_REDIS_API = "*"; + public static final String TRIM_MAXLEN_REDIS_API = "MAXLEN"; + public static final String TRIM_MINID_REDIS_API = "MINID"; + public static final String TRIM_EXACT_REDIS_API = "="; + public static final String TRIM_NOT_EXACT_REDIS_API = "~"; + public static final String TRIM_LIMIT_REDIS_API = "LIMIT"; + + /** If set, the new entry will be added with this id. */ + private final String id; + + /** + * If set to false, a new stream won't be created if no stream matches the given key. + *
+ * Equivalent to NOMKSTREAM in the Redis API. + */ + private final Boolean makeStream; + + /** If set, the add operation will also trim the older entries in the stream. */ + private final StreamTrimOptions trim; + + public abstract static class StreamTrimOptions { + /** + * If `true`, the stream will be trimmed exactly. Equivalent to `=` in the Redis API. Otherwise + * the stream will be trimmed in a near-exact manner, which is more efficient, equivalent to `~` + * in the Redis API. + */ + protected Boolean exact; + + /** If set, sets the maximal amount of entries that will be deleted. */ + protected Long limit; + + protected abstract String getMethod(); + + protected abstract String getThreshold(); + + public List addTrimOptions() { + List optionArgs = new ArrayList<>(); + + optionArgs.add(this.getMethod()); + optionArgs.add(this.exact ? TRIM_EXACT_REDIS_API : TRIM_NOT_EXACT_REDIS_API); + optionArgs.add(this.getThreshold()); + + if (this.limit != null) { + optionArgs.add(TRIM_LIMIT_REDIS_API); + optionArgs.add(this.limit.toString()); + } + + return optionArgs; + } + } + + public static class MinId extends StreamTrimOptions { + /** Trim the stream according to entry ID. Equivalent to MINID in the Redis API. */ + private final String threshold; + + public MinId(Boolean exact, String threshold) { + this.threshold = threshold; + this.exact = exact; + } + + public MinId(Boolean exact, String threshold, Long limit) { + this.threshold = threshold; + this.exact = exact; + this.limit = limit; + } + + public String getMethod() { + return TRIM_MINID_REDIS_API; + } + + public String getThreshold() { + return threshold; + } + } + + public static class Maxlen extends StreamTrimOptions { + /** + * Trim the stream according to length.
+ * Equivalent to MAXLEN in the Redis API. + */ + private final Long threshold; + + public Maxlen(Boolean exact, Long threshold) { + this.threshold = threshold; + this.exact = exact; + } + + public Maxlen(Boolean exact, Long threshold, Long limit) { + this.threshold = threshold; + this.exact = exact; + this.limit = limit; + } + + public String getMethod() { + return TRIM_MAXLEN_REDIS_API; + } + + public String getThreshold() { + return threshold.toString(); + } + } + + /** + * Converts options for Xadd into a String[]. + * + * @return String[] + */ + public String[] toArgs() { + List optionArgs = new ArrayList<>(); + + if (makeStream != null && !makeStream) { + optionArgs.add(NO_MAKE_STREAM_REDIS_API); + } + + if (trim != null) { + optionArgs.addAll(trim.addTrimOptions()); + } + + if (id != null) { + optionArgs.add(id); + } else { + optionArgs.add(ID_WILDCARD_REDIS_API); + } + + return optionArgs.toArray(new String[0]); + } +} diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 8647d6a741..8dead8061a 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -5,7 +5,14 @@ import static glide.api.models.commands.SetOptions.ConditionalSet.ONLY_IF_DOES_NOT_EXIST; import static glide.api.models.commands.SetOptions.ConditionalSet.ONLY_IF_EXISTS; import static glide.api.models.commands.SetOptions.RETURN_OLD_VALUE; +import static glide.api.models.commands.StreamAddOptions.NO_MAKE_STREAM_REDIS_API; +import static glide.api.models.commands.StreamAddOptions.TRIM_EXACT_REDIS_API; +import static glide.api.models.commands.StreamAddOptions.TRIM_LIMIT_REDIS_API; +import static glide.api.models.commands.StreamAddOptions.TRIM_MAXLEN_REDIS_API; +import static glide.api.models.commands.StreamAddOptions.TRIM_MINID_REDIS_API; +import static glide.api.models.commands.StreamAddOptions.TRIM_NOT_EXACT_REDIS_API; import static glide.utils.ArrayTransformUtils.concatenateArrays; +import static glide.utils.ArrayTransformUtils.convertMapToKeyValueStringArray; import static glide.utils.ArrayTransformUtils.convertMapToValueKeyStringArray; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -69,6 +76,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.Type; import static redis_request.RedisRequestOuterClass.RequestType.Unlink; import static redis_request.RedisRequestOuterClass.RequestType.ZScore; +import static redis_request.RedisRequestOuterClass.RequestType.XAdd; import static redis_request.RedisRequestOuterClass.RequestType.Zadd; import static redis_request.RedisRequestOuterClass.RequestType.Zcard; import static redis_request.RedisRequestOuterClass.RequestType.Zrem; @@ -79,6 +87,7 @@ import glide.api.models.commands.ScriptOptions; import glide.api.models.commands.SetOptions; import glide.api.models.commands.SetOptions.Expiry; +import glide.api.models.commands.StreamAddOptions; import glide.api.models.commands.ZaddOptions; import glide.managers.CommandManager; import glide.managers.ConnectionManager; @@ -90,8 +99,12 @@ import java.util.concurrent.CompletableFuture; import lombok.SneakyThrows; import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.tuple.Pair; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; public class RedisClientTest { @@ -1814,6 +1827,174 @@ public void zscore_returns_success() { assertEquals(value, payload); } + + @SneakyThrows + @Test + public void xadd_returns_success() { + // setup + String key = "testKey"; + Map fieldValues = new LinkedHashMap<>(); + fieldValues.put("testField1", "testValue1"); + fieldValues.put("testField2", "testValue2"); + String[] fieldValuesArgs = convertMapToKeyValueStringArray(fieldValues); + String[] arguments = new String[] {key, "*"}; + arguments = ArrayUtils.addAll(arguments, fieldValuesArgs); + String returnId = "testId"; + + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn(returnId); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(XAdd), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.xadd(key, fieldValues); + String payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(returnId, payload); + } + + @SneakyThrows + @Test + public void xadd_with_nomakestream_maxlen_options_returns_success() { + // setup + String key = "testKey"; + Map fieldValues = new LinkedHashMap<>(); + fieldValues.put("testField1", "testValue1"); + fieldValues.put("testField2", "testValue2"); + StreamAddOptions options = + StreamAddOptions.builder() + .id("id") + .makeStream(false) + .trim(new StreamAddOptions.Maxlen(true, 5L)) + .build(); + + String[] arguments = + new String[] { + key, + NO_MAKE_STREAM_REDIS_API, + TRIM_MAXLEN_REDIS_API, + TRIM_EXACT_REDIS_API, + Long.toString(5L), + "id" + }; + arguments = ArrayUtils.addAll(arguments, convertMapToKeyValueStringArray(fieldValues)); + + String returnId = "testId"; + + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn(returnId); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(XAdd), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.xadd(key, fieldValues, options); + String payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(returnId, payload); + } + + private static List getStreamAddOptions() { + return List.of( + Arguments.of( + Pair.of( + // MAXLEN with LIMIT + StreamAddOptions.builder() + .id("id") + .makeStream(Boolean.TRUE) + .trim(new StreamAddOptions.Maxlen(Boolean.TRUE, 5L, 10L)) + .build(), + new String[] { + "testKey", + TRIM_MAXLEN_REDIS_API, + TRIM_EXACT_REDIS_API, + Long.toString(5L), + TRIM_LIMIT_REDIS_API, + Long.toString(10L), + "id" + }), + Pair.of( + // MAXLEN with non exact match + StreamAddOptions.builder() + .makeStream(Boolean.FALSE) + .trim(new StreamAddOptions.Maxlen(Boolean.FALSE, 2L)) + .build(), + new String[] { + "testKey", + NO_MAKE_STREAM_REDIS_API, + TRIM_MAXLEN_REDIS_API, + TRIM_NOT_EXACT_REDIS_API, + Long.toString(2L), + "*" + }), + Pair.of( + // MIN ID with LIMIT + StreamAddOptions.builder() + .id("id") + .makeStream(Boolean.TRUE) + .trim(new StreamAddOptions.MinId(Boolean.TRUE, "testKey", 10L)) + .build(), + new String[] { + "testKey", + TRIM_MINID_REDIS_API, + TRIM_EXACT_REDIS_API, + Long.toString(5L), + TRIM_LIMIT_REDIS_API, + Long.toString(10L), + "id" + }), + Pair.of( + // MIN ID with non exact match + StreamAddOptions.builder() + .makeStream(Boolean.FALSE) + .trim(new StreamAddOptions.MinId(Boolean.FALSE, "testKey")) + .build(), + new String[] { + "testKey", + NO_MAKE_STREAM_REDIS_API, + TRIM_MINID_REDIS_API, + TRIM_NOT_EXACT_REDIS_API, + Long.toString(5L), + "*" + }))); + } + + @SneakyThrows + @ParameterizedTest + @MethodSource("getStreamAddOptions") + public void xadd_with_options_returns_success(Pair input) { + // setup + String key = "testKey"; + Map fieldValues = new LinkedHashMap<>(); + fieldValues.put("testField1", "testValue1"); + fieldValues.put("testField2", "testValue2"); + String[] arguments = + ArrayUtils.addAll(input.getRight(), convertMapToKeyValueStringArray(fieldValues)); + String returnId = "testId"; + + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn(returnId); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(XAdd), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.xadd(key, fieldValues, input.getLeft()); + String payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(returnId, payload); + } + @SneakyThrows @Test public void type_returns_success() { diff --git a/java/client/src/test/java/glide/api/models/TransactionTests.java b/java/client/src/test/java/glide/api/models/TransactionTests.java index 1aa39e169f..e6542cc9b4 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -55,6 +55,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.Type; import static redis_request.RedisRequestOuterClass.RequestType.Unlink; import static redis_request.RedisRequestOuterClass.RequestType.ZScore; +import static redis_request.RedisRequestOuterClass.RequestType.XAdd; import static redis_request.RedisRequestOuterClass.RequestType.Zadd; import static redis_request.RedisRequestOuterClass.RequestType.Zcard; import static redis_request.RedisRequestOuterClass.RequestType.Zrem; @@ -62,6 +63,7 @@ import glide.api.models.commands.ExpireOptions; import glide.api.models.commands.InfoOptions; import glide.api.models.commands.SetOptions; +import glide.api.models.commands.StreamAddOptions; import glide.api.models.commands.ZaddOptions; import java.util.LinkedHashMap; import java.util.LinkedList; @@ -391,6 +393,17 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) transaction.zscore("key", "member"); results.add(Pair.of(ZScore, ArgsArray.newBuilder().addArgs("key").addArgs("member").build())); + transaction.xadd("key", Map.of("field1", "foo1"), StreamAddOptions.builder().id("id").build()); + results.add( + Pair.of( + XAdd, + ArgsArray.newBuilder() + .addArgs("key") + .addArgs("id") + .addArgs("field1") + .addArgs("foo1") + .build())); + transaction.time(); results.add(Pair.of(Time, ArgsArray.newBuilder().build())); diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 84d59a0fe5..b450fb69bc 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -23,6 +23,7 @@ import glide.api.models.commands.ExpireOptions; import glide.api.models.commands.ScriptOptions; import glide.api.models.commands.SetOptions; +import glide.api.models.commands.StreamAddOptions; import glide.api.models.commands.ZaddOptions; import glide.api.models.configuration.NodeAddress; import glide.api.models.configuration.RedisClientConfiguration; @@ -1133,6 +1134,94 @@ public void zscore(BaseClient client) { assertTrue(executionException.getCause() instanceof RequestException); } + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void xadd(BaseClient client) { + String key = UUID.randomUUID().toString(); + String field1 = UUID.randomUUID().toString(); + String field2 = UUID.randomUUID().toString(); + + assertNull( + client + .xadd( + key, + Map.of(field1, "foo0", field2, "bar0"), + StreamAddOptions.builder().makeStream(Boolean.FALSE).build()) + .get()); + + String timestamp1 = "0-1"; + assertEquals( + timestamp1, + client + .xadd( + key, + Map.of(field1, "foo1", field2, "bar1"), + StreamAddOptions.builder().id(timestamp1).build()) + .get()); + + assertNotNull(client.xadd(key, Map.of(field1, "foo2", field2, "bar2")).get()); + if (client instanceof RedisClient) { + assertEquals(2L, ((RedisClient) client).customCommand(new String[] {"XLEN", key}).get()); + } else if (client instanceof RedisClusterClient) { + assertEquals( + 2L, + ((RedisClusterClient) client) + .customCommand(new String[] {"XLEN", key}) + .get() + .getSingleValue()); + } + + // this will trim the first entry. + String id = + client + .xadd( + key, + Map.of(field1, "foo3", field2, "bar3"), + StreamAddOptions.builder() + .trim(new StreamAddOptions.Maxlen(Boolean.TRUE, 2L)) + .build()) + .get(); + assertNotNull(id); + if (client instanceof RedisClient) { + assertEquals(2L, ((RedisClient) client).customCommand(new String[] {"XLEN", key}).get()); + } else if (client instanceof RedisClusterClient) { + assertEquals( + 2L, + ((RedisClusterClient) client) + .customCommand(new String[] {"XLEN", key}) + .get() + .getSingleValue()); + } + + // this will trim the second entry. + assertNotNull( + client + .xadd( + key, + Map.of(field1, "foo4", field2, "bar4"), + StreamAddOptions.builder() + .trim(new StreamAddOptions.MinId(Boolean.TRUE, id)) + .build()) + .get()); + if (client instanceof RedisClient) { + assertEquals(2L, ((RedisClient) client).customCommand(new String[] {"XLEN", key}).get()); + } else if (client instanceof RedisClusterClient) { + assertEquals( + 2L, + ((RedisClusterClient) client) + .customCommand(new String[] {"XLEN", key}) + .get() + .getSingleValue()); + } + + /** + * TODO add test to XTRIM on maxlen expect( await client.xtrim(key, { method: "maxlen", + * threshold: 1, exact: true, }), ).toEqual(1); expect(await client.customCommand(["XLEN", + * key])).toEqual(1); + */ + } + @SneakyThrows @ParameterizedTest @MethodSource("getClients") From bd41403873e582a753ecaca7cb6487abc57846a8 Mon Sep 17 00:00:00 2001 From: Andrew Carbonetto Date: Fri, 22 Mar 2024 11:22:37 -0700 Subject: [PATCH 2/7] Update javadoc; add option tests Signed-off-by: Andrew Carbonetto --- java/client/src/main/java/glide/api/BaseClient.java | 2 +- .../java/glide/api/commands/StreamBaseCommands.java | 10 +++++++--- .../main/java/glide/api/models/BaseTransaction.java | 2 +- .../src/test/java/glide/api/RedisClientTest.java | 10 +++++++--- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 9072eb9627..82c9e60883 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -631,7 +631,7 @@ public CompletableFuture xadd(@NonNull String key, Map v @Override public CompletableFuture xadd( - @NonNull String key, Map values, StreamAddOptions options) { + @NonNull String key, @NonNull Map values, @NonNull StreamAddOptions options) { String[] arguments = ArrayUtils.addAll( ArrayUtils.addFirst(options.toArgs(), key), diff --git a/java/client/src/main/java/glide/api/commands/StreamBaseCommands.java b/java/client/src/main/java/glide/api/commands/StreamBaseCommands.java index 837d19ec85..2dde99d820 100644 --- a/java/client/src/main/java/glide/api/commands/StreamBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/StreamBaseCommands.java @@ -34,13 +34,17 @@ public interface StreamBaseCommands { * @see redis.io for details. * @param key The key of the stream. * @param values field-value pairs to be added to the entry. - * @param options options. + * @param options Stream add options. * @return The id of the added entry, or null if options.makeStream is * set to false and no stream with the matching key exists. * @example *
{@code
-     * String streamId = client.xadd("key", Map.of("name", "Sara", "surname", "OConnor").get();
-     * System.out.println("Stream: " + streamId);
+     * // Stream options to not make the stream if "key" is not a stream, as use stream id of "sid"
+     * StreamAddOptions options = StreamAddOptions.builder().id("sid").makeStream(Boolean.FALSE).build();
+     * String streamId = client.xadd("key", Map.of("name", "Sara", "surname", "OConnor"), options).get();
+     * if (streamId != null) {
+     *     assert streamId.equals("sid");
+     * }
      * }
*/ CompletableFuture xadd(String key, Map values, StreamAddOptions options); diff --git a/java/client/src/main/java/glide/api/models/BaseTransaction.java b/java/client/src/main/java/glide/api/models/BaseTransaction.java index 722b9d506b..fdfd71f3f6 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -1313,7 +1313,7 @@ public T xadd(String key, Map values) { * @see redis.io for details. * @param key The key of the stream. * @param values field-value pairs to be added to the entry. - * @param options options. + * @param options Stream add options. * @return Command Response - The id of the added entry, or null if * options.makeStream is set to false and no stream with the matching * key exists. diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 8dead8061a..17a11a3580 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -1904,6 +1904,10 @@ public void xadd_with_nomakestream_maxlen_options_returns_success() { private static List getStreamAddOptions() { return List.of( Arguments.of( + Pair.of( + // no TRIM option + StreamAddOptions.builder().id("id").makeStream(Boolean.FALSE).build(), + new String[] {"testKey", NO_MAKE_STREAM_REDIS_API, "id"}), Pair.of( // MAXLEN with LIMIT StreamAddOptions.builder() @@ -1969,14 +1973,14 @@ private static List getStreamAddOptions() { @SneakyThrows @ParameterizedTest @MethodSource("getStreamAddOptions") - public void xadd_with_options_returns_success(Pair input) { + public void xadd_with_options_returns_success(Pair optionAndArgs) { // setup String key = "testKey"; Map fieldValues = new LinkedHashMap<>(); fieldValues.put("testField1", "testValue1"); fieldValues.put("testField2", "testValue2"); String[] arguments = - ArrayUtils.addAll(input.getRight(), convertMapToKeyValueStringArray(fieldValues)); + ArrayUtils.addAll(optionAndArgs.getRight(), convertMapToKeyValueStringArray(fieldValues)); String returnId = "testId"; CompletableFuture testResponse = mock(CompletableFuture.class); @@ -1987,7 +1991,7 @@ public void xadd_with_options_returns_success(Pair i .thenReturn(testResponse); // exercise - CompletableFuture response = service.xadd(key, fieldValues, input.getLeft()); + CompletableFuture response = service.xadd(key, fieldValues, optionAndArgs.getLeft()); String payload = response.get(); // verify From 3907a6d917a9073a2f19404df7f6fe1d8f7609da Mon Sep 17 00:00:00 2001 From: Andrew Carbonetto Date: Fri, 22 Mar 2024 15:26:23 -0700 Subject: [PATCH 3/7] Address comments Signed-off-by: Andrew Carbonetto --- java/client/src/main/java/glide/api/BaseClient.java | 8 ++++---- .../java/glide/api/commands/StreamBaseCommands.java | 2 +- .../main/java/glide/api/models/BaseTransaction.java | 13 +++++++------ .../glide/api/models/commands/StreamAddOptions.java | 13 +++++++------ .../java/glide/api/models/TransactionTests.java | 11 +++++++++++ 5 files changed, 30 insertions(+), 17 deletions(-) diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 82c9e60883..2192592afe 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -625,7 +625,7 @@ public CompletableFuture zscore(@NonNull String key, @NonNull String mem } @Override - public CompletableFuture xadd(@NonNull String key, Map values) { + public CompletableFuture xadd(@NonNull String key, @NonNull Map values) { return xadd(key, values, StreamAddOptions.builder().build()); } @@ -633,9 +633,9 @@ public CompletableFuture xadd(@NonNull String key, Map v public CompletableFuture xadd( @NonNull String key, @NonNull Map values, @NonNull StreamAddOptions options) { String[] arguments = - ArrayUtils.addAll( - ArrayUtils.addFirst(options.toArgs(), key), - ArrayTransformUtils.convertMapToKeyValueStringArray(values)); + ArrayUtils.addAll( + ArrayUtils.addFirst(options.toArgs(), key), + convertMapToKeyValueStringArray(values)); return commandManager.submitNewCommand(XAdd, arguments, this::handleStringOrNullResponse); } diff --git a/java/client/src/main/java/glide/api/commands/StreamBaseCommands.java b/java/client/src/main/java/glide/api/commands/StreamBaseCommands.java index 2dde99d820..fcc00dc75b 100644 --- a/java/client/src/main/java/glide/api/commands/StreamBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/StreamBaseCommands.java @@ -39,7 +39,7 @@ public interface StreamBaseCommands { * set to false and no stream with the matching key exists. * @example *
{@code
-     * // Stream options to not make the stream if "key" is not a stream, as use stream id of "sid"
+     * // Option to use the existing stream, or return null if the stream doesn't already exist at "key"
      * StreamAddOptions options = StreamAddOptions.builder().id("sid").makeStream(Boolean.FALSE).build();
      * String streamId = client.xadd("key", Map.of("name", "Sara", "surname", "OConnor"), options).get();
      * if (streamId != null) {
diff --git a/java/client/src/main/java/glide/api/models/BaseTransaction.java b/java/client/src/main/java/glide/api/models/BaseTransaction.java
index fdfd71f3f6..57af8db33c 100644
--- a/java/client/src/main/java/glide/api/models/BaseTransaction.java
+++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java
@@ -1303,8 +1303,9 @@ public T zscore(@NonNull String key, @NonNull String member) {
      * @param values field-value pairs to be added to the entry.
      * @return Command Response - The id of the added entry.
      */
-    public T xadd(String key, Map values) {
-        return this.xadd(key, values, StreamAddOptions.builder().build());
+    public T xadd(@NonNull String key, @NonNull Map values) {
+        this.xadd(key, values, StreamAddOptions.builder().build());
+        return getThis();
     }
 
     /**
@@ -1318,11 +1319,11 @@ public T xadd(String key, Map values) {
      *     options.makeStream is set to false and no stream with the matching
      *     key exists.
      */
-    public T xadd(String key, Map values, StreamAddOptions options) {
+    public T xadd(@NonNull String key, @NonNull Map values, @NonNull StreamAddOptions options) {
         String[] arguments =
-            ArrayUtils.addAll(
-                ArrayUtils.addFirst(options.toArgs(), key),
-                ArrayTransformUtils.convertMapToKeyValueStringArray(values));
+                ArrayUtils.addAll(
+                        ArrayUtils.addFirst(options.toArgs(), key),
+                        convertMapToKeyValueStringArray(values));
         ArgsArray commandArgs = buildArgs(arguments);
         protobufTransaction.addCommands(buildCommand(XAdd, commandArgs));
         return getThis();
diff --git a/java/client/src/main/java/glide/api/models/commands/StreamAddOptions.java b/java/client/src/main/java/glide/api/models/commands/StreamAddOptions.java
index e1f1b99153..fe682d5db9 100644
--- a/java/client/src/main/java/glide/api/models/commands/StreamAddOptions.java
+++ b/java/client/src/main/java/glide/api/models/commands/StreamAddOptions.java
@@ -5,6 +5,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import lombok.Builder;
+import lombok.NonNull;
 
 /**
  * Optional arguments to {@link StreamBaseCommands#xadd}
@@ -37,8 +38,8 @@ public final class StreamAddOptions {
 
     public abstract static class StreamTrimOptions {
         /**
-         * If `true`, the stream will be trimmed exactly. Equivalent to `=` in the Redis API. Otherwise
-         * the stream will be trimmed in a near-exact manner, which is more efficient, equivalent to `~`
+         * If true, the stream will be trimmed exactly. Equivalent to = in the Redis API. Otherwise,
+         * the stream will be trimmed in a near-exact manner, which is more efficient, equivalent to ~
          * in the Redis API.
          */
         protected Boolean exact;
@@ -70,12 +71,12 @@ public static class MinId extends StreamTrimOptions {
         /** Trim the stream according to entry ID. Equivalent to MINID in the Redis API. */
         private final String threshold;
 
-        public MinId(Boolean exact, String threshold) {
+        public MinId(boolean exact, @NonNull String threshold) {
             this.threshold = threshold;
             this.exact = exact;
         }
 
-        public MinId(Boolean exact, String threshold, Long limit) {
+        public MinId(boolean exact, @NonNull String threshold, long limit) {
             this.threshold = threshold;
             this.exact = exact;
             this.limit = limit;
@@ -97,12 +98,12 @@ public static class Maxlen extends StreamTrimOptions {
          */
         private final Long threshold;
 
-        public Maxlen(Boolean exact, Long threshold) {
+        public Maxlen(boolean exact, long threshold) {
             this.threshold = threshold;
             this.exact = exact;
         }
 
-        public Maxlen(Boolean exact, Long threshold, Long limit) {
+        public Maxlen(boolean exact, long threshold, long limit) {
             this.threshold = threshold;
             this.exact = exact;
             this.limit = limit;
diff --git a/java/client/src/test/java/glide/api/models/TransactionTests.java b/java/client/src/test/java/glide/api/models/TransactionTests.java
index e6542cc9b4..5a6d55f6ad 100644
--- a/java/client/src/test/java/glide/api/models/TransactionTests.java
+++ b/java/client/src/test/java/glide/api/models/TransactionTests.java
@@ -393,6 +393,17 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction)
         transaction.zscore("key", "member");
         results.add(Pair.of(ZScore, ArgsArray.newBuilder().addArgs("key").addArgs("member").build()));
 
+        transaction.xadd("key", Map.of("field1", "foo1"));
+        results.add(
+            Pair.of(
+                XAdd,
+                ArgsArray.newBuilder()
+                    .addArgs("key")
+                    .addArgs("*")
+                    .addArgs("field1")
+                    .addArgs("foo1")
+                    .build()));
+
         transaction.xadd("key", Map.of("field1", "foo1"), StreamAddOptions.builder().id("id").build());
         results.add(
             Pair.of(

From 98aafb5b899da64273ebd9d09043af1f72e7ef1c Mon Sep 17 00:00:00 2001
From: Andrew Carbonetto 
Date: Fri, 22 Mar 2024 18:12:31 -0700
Subject: [PATCH 4/7] Spotless

Signed-off-by: Andrew Carbonetto 
---
 .../src/main/java/glide/api/BaseClient.java      |  3 +--
 .../java/glide/api/models/BaseTransaction.java   |  7 +++----
 .../api/models/commands/StreamAddOptions.java    |  6 +++---
 .../java/glide/api/models/TransactionTests.java  | 16 ++++++++--------
 4 files changed, 15 insertions(+), 17 deletions(-)

diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java
index 2192592afe..a1e872abb8 100644
--- a/java/client/src/main/java/glide/api/BaseClient.java
+++ b/java/client/src/main/java/glide/api/BaseClient.java
@@ -634,8 +634,7 @@ public CompletableFuture xadd(
             @NonNull String key, @NonNull Map values, @NonNull StreamAddOptions options) {
         String[] arguments =
                 ArrayUtils.addAll(
-                        ArrayUtils.addFirst(options.toArgs(), key),
-                        convertMapToKeyValueStringArray(values));
+                        ArrayUtils.addFirst(options.toArgs(), key), convertMapToKeyValueStringArray(values));
         return commandManager.submitNewCommand(XAdd, arguments, this::handleStringOrNullResponse);
     }
 
diff --git a/java/client/src/main/java/glide/api/models/BaseTransaction.java b/java/client/src/main/java/glide/api/models/BaseTransaction.java
index 57af8db33c..54a31fa061 100644
--- a/java/client/src/main/java/glide/api/models/BaseTransaction.java
+++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java
@@ -70,7 +70,6 @@
 import glide.api.models.commands.SetOptions.SetOptionsBuilder;
 import glide.api.models.commands.StreamAddOptions;
 import glide.api.models.commands.ZaddOptions;
-import glide.utils.ArrayTransformUtils;
 import java.util.Map;
 import lombok.Getter;
 import lombok.NonNull;
@@ -1319,11 +1318,11 @@ public T xadd(@NonNull String key, @NonNull Map values) {
      *     options.makeStream is set to false and no stream with the matching
      *     key exists.
      */
-    public T xadd(@NonNull String key, @NonNull Map values, @NonNull StreamAddOptions options) {
+    public T xadd(
+            @NonNull String key, @NonNull Map values, @NonNull StreamAddOptions options) {
         String[] arguments =
                 ArrayUtils.addAll(
-                        ArrayUtils.addFirst(options.toArgs(), key),
-                        convertMapToKeyValueStringArray(values));
+                        ArrayUtils.addFirst(options.toArgs(), key), convertMapToKeyValueStringArray(values));
         ArgsArray commandArgs = buildArgs(arguments);
         protobufTransaction.addCommands(buildCommand(XAdd, commandArgs));
         return getThis();
diff --git a/java/client/src/main/java/glide/api/models/commands/StreamAddOptions.java b/java/client/src/main/java/glide/api/models/commands/StreamAddOptions.java
index fe682d5db9..3e13441fc9 100644
--- a/java/client/src/main/java/glide/api/models/commands/StreamAddOptions.java
+++ b/java/client/src/main/java/glide/api/models/commands/StreamAddOptions.java
@@ -38,9 +38,9 @@ public final class StreamAddOptions {
 
     public abstract static class StreamTrimOptions {
         /**
-         * If true, the stream will be trimmed exactly. Equivalent to = in the Redis API. Otherwise,
-         * the stream will be trimmed in a near-exact manner, which is more efficient, equivalent to ~
-         * in the Redis API.
+         * If true, the stream will be trimmed exactly. Equivalent to = in the
+         * Redis API. Otherwise, the stream will be trimmed in a near-exact manner, which is more
+         * efficient, equivalent to ~ in the Redis API.
          */
         protected Boolean exact;
 
diff --git a/java/client/src/test/java/glide/api/models/TransactionTests.java b/java/client/src/test/java/glide/api/models/TransactionTests.java
index 5a6d55f6ad..734f20ed7f 100644
--- a/java/client/src/test/java/glide/api/models/TransactionTests.java
+++ b/java/client/src/test/java/glide/api/models/TransactionTests.java
@@ -395,14 +395,14 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction)
 
         transaction.xadd("key", Map.of("field1", "foo1"));
         results.add(
-            Pair.of(
-                XAdd,
-                ArgsArray.newBuilder()
-                    .addArgs("key")
-                    .addArgs("*")
-                    .addArgs("field1")
-                    .addArgs("foo1")
-                    .build()));
+                Pair.of(
+                        XAdd,
+                        ArgsArray.newBuilder()
+                                .addArgs("key")
+                                .addArgs("*")
+                                .addArgs("field1")
+                                .addArgs("foo1")
+                                .build()));
 
         transaction.xadd("key", Map.of("field1", "foo1"), StreamAddOptions.builder().id("id").build());
         results.add(

From f4735dd7373a61258011378f04fb6d811b91029e Mon Sep 17 00:00:00 2001
From: Andrew Carbonetto 
Date: Mon, 25 Mar 2024 16:39:09 -0700
Subject: [PATCH 5/7] Update StreamAddOptions for comments

Signed-off-by: Andrew Carbonetto 
---
 .../api/commands/StreamBaseCommands.java      |   8 +-
 .../glide/api/models/BaseTransaction.java     |   8 +-
 .../api/models/commands/StreamAddOptions.java |  48 +++++--
 .../test/java/glide/api/RedisClientTest.java  | 130 +++++++++---------
 .../test/java/glide/SharedCommandTests.java   |  16 +--
 5 files changed, 120 insertions(+), 90 deletions(-)

diff --git a/java/client/src/main/java/glide/api/commands/StreamBaseCommands.java b/java/client/src/main/java/glide/api/commands/StreamBaseCommands.java
index fcc00dc75b..5577655ab8 100644
--- a/java/client/src/main/java/glide/api/commands/StreamBaseCommands.java
+++ b/java/client/src/main/java/glide/api/commands/StreamBaseCommands.java
@@ -18,7 +18,7 @@ public interface StreamBaseCommands {
      *
      * @see redis.io for details.
      * @param key The key of the stream.
-     * @param values field-value pairs to be added to the entry.
+     * @param values Field-value pairs to be added to the entry.
      * @return The id of the added entry.
      * @example
      *     
{@code
@@ -33,10 +33,10 @@ public interface StreamBaseCommands {
      *
      * @see redis.io for details.
      * @param key The key of the stream.
-     * @param values field-value pairs to be added to the entry.
+     * @param values Field-value pairs to be added to the entry.
      * @param options Stream add options.
-     * @return The id of the added entry, or null if options.makeStream is
-     *     set to false and no stream with the matching key exists.
+     * @return The id of the added entry, or null if {@link StreamAddOptions#makeStream}
+     *     is set to false and no stream with the matching key exists.
      * @example
      *     
{@code
      * // Option to use the existing stream, or return null if the stream doesn't already exist at "key"
diff --git a/java/client/src/main/java/glide/api/models/BaseTransaction.java b/java/client/src/main/java/glide/api/models/BaseTransaction.java
index 54a31fa061..9f145ebcb8 100644
--- a/java/client/src/main/java/glide/api/models/BaseTransaction.java
+++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java
@@ -1299,7 +1299,7 @@ public T zscore(@NonNull String key, @NonNull String member) {
      *
      * @see redis.io for details.
      * @param key The key of the stream.
-     * @param values field-value pairs to be added to the entry.
+     * @param values Field-value pairs to be added to the entry.
      * @return Command Response - The id of the added entry.
      */
     public T xadd(@NonNull String key, @NonNull Map values) {
@@ -1312,10 +1312,10 @@ public T xadd(@NonNull String key, @NonNull Map values) {
      *
      * @see redis.io for details.
      * @param key The key of the stream.
-     * @param values field-value pairs to be added to the entry.
+     * @param values Field-value pairs to be added to the entry.
      * @param options Stream add options.
-     * @return Command Response - The id of the added entry, or null if 
-     *     options.makeStream is set to false and no stream with the matching
+     * @return Command Response - The id of the added entry, or null if {@link
+     *     StreamAddOptions#makeStream} is set to false and no stream with the matching
      *     key exists.
      */
     public T xadd(
diff --git a/java/client/src/main/java/glide/api/models/commands/StreamAddOptions.java b/java/client/src/main/java/glide/api/models/commands/StreamAddOptions.java
index 3e13441fc9..f86dd18c68 100644
--- a/java/client/src/main/java/glide/api/models/commands/StreamAddOptions.java
+++ b/java/client/src/main/java/glide/api/models/commands/StreamAddOptions.java
@@ -42,7 +42,7 @@ public abstract static class StreamTrimOptions {
          * Redis API. Otherwise, the stream will be trimmed in a near-exact manner, which is more
          * efficient, equivalent to ~ in the Redis API.
          */
-        protected Boolean exact;
+        protected boolean exact;
 
         /** If set, sets the maximal amount of entries that will be deleted. */
         protected Long limit;
@@ -71,49 +71,79 @@ public static class MinId extends StreamTrimOptions {
         /** Trim the stream according to entry ID. Equivalent to MINID in the Redis API. */
         private final String threshold;
 
+        /**
+         * Create a trim option to trim stream based on stream ID.
+         *
+         * @param exact whether to match exactly on the threshold.
+         * @param threshold comparison id.
+         */
         public MinId(boolean exact, @NonNull String threshold) {
             this.threshold = threshold;
             this.exact = exact;
         }
 
+        /**
+         * Create a trim option to trim stream based on stream ID.
+         *
+         * @param exact whether to match exactly on the threshold.
+         * @param threshold comparison id.
+         * @param limit max number of stream entries to be trimmed.
+         */
         public MinId(boolean exact, @NonNull String threshold, long limit) {
             this.threshold = threshold;
             this.exact = exact;
             this.limit = limit;
         }
 
-        public String getMethod() {
+        @Override
+        protected String getMethod() {
             return TRIM_MINID_REDIS_API;
         }
 
-        public String getThreshold() {
+        @Override
+        protected String getThreshold() {
             return threshold;
         }
     }
 
-    public static class Maxlen extends StreamTrimOptions {
+    public static class MaxLen extends StreamTrimOptions {
         /**
-         * Trim the stream according to length. 
+ * Trim the stream according to length.
* Equivalent to MAXLEN in the Redis API. */ private final Long threshold; - public Maxlen(boolean exact, long threshold) { + /** + * Create a Max Length trim option to trim stream based on length. + * + * @param exact whether to match exactly on the threshold. + * @param threshold comparison count. + */ + public MaxLen(boolean exact, long threshold) { this.threshold = threshold; this.exact = exact; } - public Maxlen(boolean exact, long threshold, long limit) { + /** + * Create a Max Length trim option to trim stream entries exceeds the threshold. + * + * @param exact whether to match exactly on the threshold. + * @param threshold comparison count. + * @param limit max number of stream entries to be trimmed. + */ + public MaxLen(boolean exact, long threshold, long limit) { this.threshold = threshold; this.exact = exact; this.limit = limit; } - public String getMethod() { + @Override + protected String getMethod() { return TRIM_MAXLEN_REDIS_API; } - public String getThreshold() { + @Override + protected String getThreshold() { return threshold.toString(); } } diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 17a11a3580..3820604e0e 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -1903,71 +1903,71 @@ public void xadd_with_nomakestream_maxlen_options_returns_success() { private static List getStreamAddOptions() { return List.of( - Arguments.of( - Pair.of( - // no TRIM option - StreamAddOptions.builder().id("id").makeStream(Boolean.FALSE).build(), - new String[] {"testKey", NO_MAKE_STREAM_REDIS_API, "id"}), - Pair.of( - // MAXLEN with LIMIT - StreamAddOptions.builder() - .id("id") - .makeStream(Boolean.TRUE) - .trim(new StreamAddOptions.Maxlen(Boolean.TRUE, 5L, 10L)) - .build(), - new String[] { - "testKey", - TRIM_MAXLEN_REDIS_API, - TRIM_EXACT_REDIS_API, - Long.toString(5L), - TRIM_LIMIT_REDIS_API, - Long.toString(10L), - "id" - }), - Pair.of( - // MAXLEN with non exact match - StreamAddOptions.builder() - .makeStream(Boolean.FALSE) - .trim(new StreamAddOptions.Maxlen(Boolean.FALSE, 2L)) - .build(), - new String[] { - "testKey", - NO_MAKE_STREAM_REDIS_API, - TRIM_MAXLEN_REDIS_API, - TRIM_NOT_EXACT_REDIS_API, - Long.toString(2L), - "*" - }), - Pair.of( - // MIN ID with LIMIT - StreamAddOptions.builder() - .id("id") - .makeStream(Boolean.TRUE) - .trim(new StreamAddOptions.MinId(Boolean.TRUE, "testKey", 10L)) - .build(), - new String[] { - "testKey", - TRIM_MINID_REDIS_API, - TRIM_EXACT_REDIS_API, - Long.toString(5L), - TRIM_LIMIT_REDIS_API, - Long.toString(10L), - "id" - }), - Pair.of( - // MIN ID with non exact match - StreamAddOptions.builder() - .makeStream(Boolean.FALSE) - .trim(new StreamAddOptions.MinId(Boolean.FALSE, "testKey")) - .build(), - new String[] { - "testKey", - NO_MAKE_STREAM_REDIS_API, - TRIM_MINID_REDIS_API, - TRIM_NOT_EXACT_REDIS_API, - Long.toString(5L), - "*" - }))); + Arguments.of( + Pair.of( + // no TRIM option + StreamAddOptions.builder().id("id").makeStream(Boolean.FALSE).build(), + new String[] {"testKey", NO_MAKE_STREAM_REDIS_API, "id"}), + Pair.of( + // MAXLEN with LIMIT + StreamAddOptions.builder() + .id("id") + .makeStream(Boolean.TRUE) + .trim(new StreamAddOptions.MaxLen(Boolean.TRUE, 5L, 10L)) + .build(), + new String[] { + "testKey", + TRIM_MAXLEN_REDIS_API, + TRIM_EXACT_REDIS_API, + Long.toString(5L), + TRIM_LIMIT_REDIS_API, + Long.toString(10L), + "id" + }), + Pair.of( + // MAXLEN with non exact match + StreamAddOptions.builder() + .makeStream(Boolean.FALSE) + .trim(new StreamAddOptions.MaxLen(Boolean.FALSE, 2L)) + .build(), + new String[] { + "testKey", + NO_MAKE_STREAM_REDIS_API, + TRIM_MAXLEN_REDIS_API, + TRIM_NOT_EXACT_REDIS_API, + Long.toString(2L), + "*" + }), + Pair.of( + // MIN ID with LIMIT + StreamAddOptions.builder() + .id("id") + .makeStream(Boolean.TRUE) + .trim(new StreamAddOptions.MinId(Boolean.TRUE, "testKey", 10L)) + .build(), + new String[] { + "testKey", + TRIM_MINID_REDIS_API, + TRIM_EXACT_REDIS_API, + Long.toString(5L), + TRIM_LIMIT_REDIS_API, + Long.toString(10L), + "id" + }), + Pair.of( + // MIN ID with non exact match + StreamAddOptions.builder() + .makeStream(Boolean.FALSE) + .trim(new StreamAddOptions.MinId(Boolean.FALSE, "testKey")) + .build(), + new String[] { + "testKey", + NO_MAKE_STREAM_REDIS_API, + TRIM_MINID_REDIS_API, + TRIM_NOT_EXACT_REDIS_API, + Long.toString(5L), + "*" + }))); } @SneakyThrows diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index b450fb69bc..7d279682c6 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -1174,14 +1174,14 @@ public void xadd(BaseClient client) { // this will trim the first entry. String id = - client - .xadd( - key, - Map.of(field1, "foo3", field2, "bar3"), - StreamAddOptions.builder() - .trim(new StreamAddOptions.Maxlen(Boolean.TRUE, 2L)) - .build()) - .get(); + client + .xadd( + key, + Map.of(field1, "foo3", field2, "bar3"), + StreamAddOptions.builder() + .trim(new StreamAddOptions.MaxLen(Boolean.TRUE, 2L)) + .build()) + .get(); assertNotNull(id); if (client instanceof RedisClient) { assertEquals(2L, ((RedisClient) client).customCommand(new String[] {"XLEN", key}).get()); From 3df10210737bd698802ec4f6b319415d51754d72 Mon Sep 17 00:00:00 2001 From: Andrew Carbonetto Date: Mon, 1 Apr 2024 11:39:39 -0700 Subject: [PATCH 6/7] Add javadocs for trim options Signed-off-by: Andrew Carbonetto --- .../java/glide/api/models/commands/StreamAddOptions.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/java/client/src/main/java/glide/api/models/commands/StreamAddOptions.java b/java/client/src/main/java/glide/api/models/commands/StreamAddOptions.java index f86dd18c68..1f98dca863 100644 --- a/java/client/src/main/java/glide/api/models/commands/StreamAddOptions.java +++ b/java/client/src/main/java/glide/api/models/commands/StreamAddOptions.java @@ -51,7 +51,7 @@ public abstract static class StreamTrimOptions { protected abstract String getThreshold(); - public List addTrimOptions() { + protected List getRedisApi() { List optionArgs = new ArrayList<>(); optionArgs.add(this.getMethod()); @@ -67,6 +67,7 @@ public List addTrimOptions() { } } + /** Option to trim the stream according to minimum ID. */ public static class MinId extends StreamTrimOptions { /** Trim the stream according to entry ID. Equivalent to MINID in the Redis API. */ private final String threshold; @@ -106,6 +107,7 @@ protected String getThreshold() { } } + /** Option to trim the stream according to maximum stream length. */ public static class MaxLen extends StreamTrimOptions { /** * Trim the stream according to length.
@@ -161,7 +163,7 @@ public String[] toArgs() { } if (trim != null) { - optionArgs.addAll(trim.addTrimOptions()); + optionArgs.addAll(trim.getRedisApi()); } if (id != null) { From a19326697050e78d2a7885b00f137f3424e9f860 Mon Sep 17 00:00:00 2001 From: Andrew Carbonetto Date: Mon, 1 Apr 2024 12:11:39 -0700 Subject: [PATCH 7/7] Spotless Signed-off-by: Andrew Carbonetto --- .../src/main/java/glide/api/BaseClient.java | 1 - .../glide/api/models/BaseTransaction.java | 2 +- .../test/java/glide/api/RedisClientTest.java | 37 +++++----- .../glide/api/models/TransactionTests.java | 18 ++--- .../test/java/glide/SharedCommandTests.java | 72 +++++++++---------- 5 files changed, 64 insertions(+), 66 deletions(-) diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index a1e872abb8..a99596a4a2 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -78,7 +78,6 @@ import glide.managers.CommandManager; import glide.managers.ConnectionManager; import java.util.List; -import glide.utils.ArrayTransformUtils; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; diff --git a/java/client/src/main/java/glide/api/models/BaseTransaction.java b/java/client/src/main/java/glide/api/models/BaseTransaction.java index 9f145ebcb8..44b2414f43 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -56,8 +56,8 @@ import static redis_request.RedisRequestOuterClass.RequestType.Time; import static redis_request.RedisRequestOuterClass.RequestType.Type; import static redis_request.RedisRequestOuterClass.RequestType.Unlink; -import static redis_request.RedisRequestOuterClass.RequestType.ZScore; import static redis_request.RedisRequestOuterClass.RequestType.XAdd; +import static redis_request.RedisRequestOuterClass.RequestType.ZScore; import static redis_request.RedisRequestOuterClass.RequestType.Zadd; import static redis_request.RedisRequestOuterClass.RequestType.Zcard; import static redis_request.RedisRequestOuterClass.RequestType.Zrem; diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 3820604e0e..283057a428 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -75,8 +75,8 @@ import static redis_request.RedisRequestOuterClass.RequestType.Time; import static redis_request.RedisRequestOuterClass.RequestType.Type; import static redis_request.RedisRequestOuterClass.RequestType.Unlink; -import static redis_request.RedisRequestOuterClass.RequestType.ZScore; import static redis_request.RedisRequestOuterClass.RequestType.XAdd; +import static redis_request.RedisRequestOuterClass.RequestType.ZScore; import static redis_request.RedisRequestOuterClass.RequestType.Zadd; import static redis_request.RedisRequestOuterClass.RequestType.Zcard; import static redis_request.RedisRequestOuterClass.RequestType.Zrem; @@ -1827,7 +1827,6 @@ public void zscore_returns_success() { assertEquals(value, payload); } - @SneakyThrows @Test public void xadd_returns_success() { @@ -1846,7 +1845,7 @@ public void xadd_returns_success() { // match on protobuf request when(commandManager.submitNewCommand(eq(XAdd), eq(arguments), any())) - .thenReturn(testResponse); + .thenReturn(testResponse); // exercise CompletableFuture response = service.xadd(key, fieldValues); @@ -1866,21 +1865,21 @@ public void xadd_with_nomakestream_maxlen_options_returns_success() { fieldValues.put("testField1", "testValue1"); fieldValues.put("testField2", "testValue2"); StreamAddOptions options = - StreamAddOptions.builder() - .id("id") - .makeStream(false) - .trim(new StreamAddOptions.Maxlen(true, 5L)) - .build(); + StreamAddOptions.builder() + .id("id") + .makeStream(false) + .trim(new StreamAddOptions.MaxLen(true, 5L)) + .build(); String[] arguments = - new String[] { - key, - NO_MAKE_STREAM_REDIS_API, - TRIM_MAXLEN_REDIS_API, - TRIM_EXACT_REDIS_API, - Long.toString(5L), - "id" - }; + new String[] { + key, + NO_MAKE_STREAM_REDIS_API, + TRIM_MAXLEN_REDIS_API, + TRIM_EXACT_REDIS_API, + Long.toString(5L), + "id" + }; arguments = ArrayUtils.addAll(arguments, convertMapToKeyValueStringArray(fieldValues)); String returnId = "testId"; @@ -1890,7 +1889,7 @@ public void xadd_with_nomakestream_maxlen_options_returns_success() { // match on protobuf request when(commandManager.submitNewCommand(eq(XAdd), eq(arguments), any())) - .thenReturn(testResponse); + .thenReturn(testResponse); // exercise CompletableFuture response = service.xadd(key, fieldValues, options); @@ -1980,7 +1979,7 @@ public void xadd_with_options_returns_success(Pair o fieldValues.put("testField1", "testValue1"); fieldValues.put("testField2", "testValue2"); String[] arguments = - ArrayUtils.addAll(optionAndArgs.getRight(), convertMapToKeyValueStringArray(fieldValues)); + ArrayUtils.addAll(optionAndArgs.getRight(), convertMapToKeyValueStringArray(fieldValues)); String returnId = "testId"; CompletableFuture testResponse = mock(CompletableFuture.class); @@ -1988,7 +1987,7 @@ public void xadd_with_options_returns_success(Pair o // match on protobuf request when(commandManager.submitNewCommand(eq(XAdd), eq(arguments), any())) - .thenReturn(testResponse); + .thenReturn(testResponse); // exercise CompletableFuture response = service.xadd(key, fieldValues, optionAndArgs.getLeft()); diff --git a/java/client/src/test/java/glide/api/models/TransactionTests.java b/java/client/src/test/java/glide/api/models/TransactionTests.java index 734f20ed7f..62091d0953 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -54,8 +54,8 @@ import static redis_request.RedisRequestOuterClass.RequestType.Time; import static redis_request.RedisRequestOuterClass.RequestType.Type; import static redis_request.RedisRequestOuterClass.RequestType.Unlink; -import static redis_request.RedisRequestOuterClass.RequestType.ZScore; import static redis_request.RedisRequestOuterClass.RequestType.XAdd; +import static redis_request.RedisRequestOuterClass.RequestType.ZScore; import static redis_request.RedisRequestOuterClass.RequestType.Zadd; import static redis_request.RedisRequestOuterClass.RequestType.Zcard; import static redis_request.RedisRequestOuterClass.RequestType.Zrem; @@ -406,14 +406,14 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) transaction.xadd("key", Map.of("field1", "foo1"), StreamAddOptions.builder().id("id").build()); results.add( - Pair.of( - XAdd, - ArgsArray.newBuilder() - .addArgs("key") - .addArgs("id") - .addArgs("field1") - .addArgs("foo1") - .build())); + Pair.of( + XAdd, + ArgsArray.newBuilder() + .addArgs("key") + .addArgs("id") + .addArgs("field1") + .addArgs("foo1") + .build())); transaction.time(); results.add(Pair.of(Time, ArgsArray.newBuilder().build())); diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 7d279682c6..201ba65819 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -1143,33 +1143,33 @@ public void xadd(BaseClient client) { String field2 = UUID.randomUUID().toString(); assertNull( - client - .xadd( - key, - Map.of(field1, "foo0", field2, "bar0"), - StreamAddOptions.builder().makeStream(Boolean.FALSE).build()) - .get()); + client + .xadd( + key, + Map.of(field1, "foo0", field2, "bar0"), + StreamAddOptions.builder().makeStream(Boolean.FALSE).build()) + .get()); String timestamp1 = "0-1"; assertEquals( - timestamp1, - client - .xadd( - key, - Map.of(field1, "foo1", field2, "bar1"), - StreamAddOptions.builder().id(timestamp1).build()) - .get()); + timestamp1, + client + .xadd( + key, + Map.of(field1, "foo1", field2, "bar1"), + StreamAddOptions.builder().id(timestamp1).build()) + .get()); assertNotNull(client.xadd(key, Map.of(field1, "foo2", field2, "bar2")).get()); if (client instanceof RedisClient) { assertEquals(2L, ((RedisClient) client).customCommand(new String[] {"XLEN", key}).get()); } else if (client instanceof RedisClusterClient) { assertEquals( - 2L, - ((RedisClusterClient) client) - .customCommand(new String[] {"XLEN", key}) - .get() - .getSingleValue()); + 2L, + ((RedisClusterClient) client) + .customCommand(new String[] {"XLEN", key}) + .get() + .getSingleValue()); } // this will trim the first entry. @@ -1187,32 +1187,32 @@ public void xadd(BaseClient client) { assertEquals(2L, ((RedisClient) client).customCommand(new String[] {"XLEN", key}).get()); } else if (client instanceof RedisClusterClient) { assertEquals( - 2L, - ((RedisClusterClient) client) - .customCommand(new String[] {"XLEN", key}) - .get() - .getSingleValue()); + 2L, + ((RedisClusterClient) client) + .customCommand(new String[] {"XLEN", key}) + .get() + .getSingleValue()); } // this will trim the second entry. assertNotNull( - client - .xadd( - key, - Map.of(field1, "foo4", field2, "bar4"), - StreamAddOptions.builder() - .trim(new StreamAddOptions.MinId(Boolean.TRUE, id)) - .build()) - .get()); + client + .xadd( + key, + Map.of(field1, "foo4", field2, "bar4"), + StreamAddOptions.builder() + .trim(new StreamAddOptions.MinId(Boolean.TRUE, id)) + .build()) + .get()); if (client instanceof RedisClient) { assertEquals(2L, ((RedisClient) client).customCommand(new String[] {"XLEN", key}).get()); } else if (client instanceof RedisClusterClient) { assertEquals( - 2L, - ((RedisClusterClient) client) - .customCommand(new String[] {"XLEN", key}) - .get() - .getSingleValue()); + 2L, + ((RedisClusterClient) client) + .customCommand(new String[] {"XLEN", key}) + .get() + .getSingleValue()); } /**