From d83a89acbfcd3c8efd529b7361a691164890945c Mon Sep 17 00:00:00 2001 From: Yury-Fridlyand Date: Thu, 4 Apr 2024 10:51:46 -0700 Subject: [PATCH] Java: Add `PFADD` command. (#1221) * Add `PFADD` command. (#166) Signed-off-by: Yury-Fridlyand --- CHANGELOG.md | 2 +- glide-core/src/protobuf/redis_request.proto | 1 + glide-core/src/socket_listener.rs | 1 + .../src/main/java/glide/api/BaseClient.java | 11 +++++- .../api/commands/HyperLogLogBaseCommands.java | 38 +++++++++++++++++++ .../glide/api/models/BaseTransaction.java | 23 +++++++++++ .../test/java/glide/api/RedisClientTest.java | 26 +++++++++++++ .../glide/api/models/TransactionTests.java | 7 ++++ .../test/java/glide/SharedCommandTests.java | 17 +++++++++ .../java/glide/TransactionTestUtilities.java | 4 ++ 10 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 java/client/src/main/java/glide/api/commands/HyperLogLogBaseCommands.java diff --git a/CHANGELOG.md b/CHANGELOG.md index b09023f9f4..8be4873cdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,4 +83,4 @@ Preview release of **GLIDE for Redis** a Polyglot Redis client. -See the [README](README.md) for additional information. \ No newline at end of file +See the [README](README.md) for additional information. diff --git a/glide-core/src/protobuf/redis_request.proto b/glide-core/src/protobuf/redis_request.proto index 33e4fcea77..9c76d9e887 100644 --- a/glide-core/src/protobuf/redis_request.proto +++ b/glide-core/src/protobuf/redis_request.proto @@ -136,6 +136,7 @@ enum RequestType { DBSize = 92; Brpop = 93; Hkeys = 94; + PfAdd = 96; } message Command { diff --git a/glide-core/src/socket_listener.rs b/glide-core/src/socket_listener.rs index 2eabf6e80a..648b9e4d40 100644 --- a/glide-core/src/socket_listener.rs +++ b/glide-core/src/socket_listener.rs @@ -363,6 +363,7 @@ fn get_command(request: &Command) -> Option { RequestType::DBSize => Some(cmd("DBSIZE")), RequestType::Brpop => Some(cmd("BRPOP")), RequestType::Hkeys => Some(cmd("HKEYS")), + RequestType::PfAdd => Some(cmd("PFADD")), } } diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index ef6f87f530..1b577e0440 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -39,6 +39,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.PExpireAt; import static redis_request.RedisRequestOuterClass.RequestType.PTTL; import static redis_request.RedisRequestOuterClass.RequestType.Persist; +import static redis_request.RedisRequestOuterClass.RequestType.PfAdd; import static redis_request.RedisRequestOuterClass.RequestType.RPop; import static redis_request.RedisRequestOuterClass.RequestType.RPush; import static redis_request.RedisRequestOuterClass.RequestType.SAdd; @@ -61,6 +62,7 @@ import glide.api.commands.GenericBaseCommands; import glide.api.commands.HashBaseCommands; +import glide.api.commands.HyperLogLogBaseCommands; import glide.api.commands.ListBaseCommands; import glide.api.commands.SetBaseCommands; import glide.api.commands.SortedSetBaseCommands; @@ -105,7 +107,8 @@ public abstract class BaseClient HashBaseCommands, ListBaseCommands, SetBaseCommands, - SortedSetBaseCommands { + SortedSetBaseCommands, + HyperLogLogBaseCommands { /** Redis simple string response with "OK" */ public static final String OK = ConstantResponse.OK.toString(); @@ -732,4 +735,10 @@ public CompletableFuture> zrangeWithScores( @NonNull String key, @NonNull ScoredRangeQuery rangeQuery) { return this.zrangeWithScores(key, rangeQuery, false); } + + @Override + public CompletableFuture pfadd(@NonNull String key, @NonNull String[] elements) { + String[] arguments = ArrayUtils.addFirst(elements, key); + return commandManager.submitNewCommand(PfAdd, arguments, this::handleLongResponse); + } } diff --git a/java/client/src/main/java/glide/api/commands/HyperLogLogBaseCommands.java b/java/client/src/main/java/glide/api/commands/HyperLogLogBaseCommands.java new file mode 100644 index 0000000000..258f6c4c62 --- /dev/null +++ b/java/client/src/main/java/glide/api/commands/HyperLogLogBaseCommands.java @@ -0,0 +1,38 @@ +/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ +package glide.api.commands; + +import java.util.concurrent.CompletableFuture; + +/** + * Supports commands and transactions for the "HyperLogLog Commands" group for standalone and + * cluster clients. + * + * @see HyperLogLog Commands + */ +public interface HyperLogLogBaseCommands { + + /** + * Adds all elements to the HyperLogLog data structure stored at the specified key. + *
+ * Creates a new structure if the key does not exist. + * + *

When no elements are provided, and key exists and is a + * HyperLogLog, then no operation is performed. If key does not exist, then the + * HyperLogLog structure is created. + * + * @see redis.io for details. + * @param key The key of the HyperLogLog data structure to add elements into. + * @param elements An array of members to add to the HyperLogLog stored at key. + * @return If the HyperLogLog is newly created, or if the HyperLogLog approximated cardinality is + * altered, then returns 1. Otherwise, returns 0. + * @example + *

{@code
+     * Long result = client.pfadd("hll_1", new String[] { "a", "b", "c" }).get();
+     * assert result == 1L; // A data structure was created or modified
+     *
+     * result = client.pfadd("hll_2", new String[0]).get();
+     * assert result == 1L; // A new empty data structure was created
+     * }
+ */ + CompletableFuture pfadd(String key, String[] elements); +} 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 0e84bd6007..6f6c2b0dd5 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -48,6 +48,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.PExpireAt; import static redis_request.RedisRequestOuterClass.RequestType.PTTL; import static redis_request.RedisRequestOuterClass.RequestType.Persist; +import static redis_request.RedisRequestOuterClass.RequestType.PfAdd; import static redis_request.RedisRequestOuterClass.RequestType.Ping; import static redis_request.RedisRequestOuterClass.RequestType.RPop; import static redis_request.RedisRequestOuterClass.RequestType.RPush; @@ -1618,6 +1619,28 @@ public T zrangeWithScores(@NonNull String key, @NonNull ScoredRangeQuery rangeQu return getThis().zrangeWithScores(key, rangeQuery, false); } + /** + * Adds all elements to the HyperLogLog data structure stored at the specified key. + *
+ * Creates a new structure if the key does not exist. + * + *

When no elements are provided, and key exists and is a + * HyperLogLog, then no operation is performed. If key does not exist, then the + * HyperLogLog structure is created. + * + * @see redis.io for details. + * @param key The key of the HyperLogLog data structure to add elements into. + * @param elements An array of members to add to the HyperLogLog stored at key. + * @return Command Response - If the HyperLogLog is newly created, or if the HyperLogLog + * approximated cardinality is altered, then returns 1. Otherwise, returns + * 0. + */ + public T pfadd(@NonNull String key, @NonNull String[] elements) { + ArgsArray commandArgs = buildArgs(ArrayUtils.addFirst(elements, key)); + protobufTransaction.addCommands(buildCommand(PfAdd, commandArgs)); + return getThis(); + } + /** Build protobuf {@link Command} object for given command and arguments. */ protected Command buildCommand(RequestType requestType) { return buildCommand(requestType, buildArgs()); diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 7191ebed0b..eca029a1d5 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -60,6 +60,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.PExpireAt; import static redis_request.RedisRequestOuterClass.RequestType.PTTL; import static redis_request.RedisRequestOuterClass.RequestType.Persist; +import static redis_request.RedisRequestOuterClass.RequestType.PfAdd; import static redis_request.RedisRequestOuterClass.RequestType.Ping; import static redis_request.RedisRequestOuterClass.RequestType.RPop; import static redis_request.RedisRequestOuterClass.RequestType.RPush; @@ -2242,4 +2243,29 @@ public void time_returns_success() { assertEquals(testResponse, response); assertEquals(payload, response.get()); } + + @SneakyThrows + @Test + public void pfadd_returns_success() { + // setup + String key = "testKey"; + String[] elements = new String[] {"a", "b", "c"}; + String[] arguments = new String[] {key, "a", "b", "c"}; + Long value = 1L; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(PfAdd), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.pfadd(key, elements); + Long payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(payload, response.get()); + } } 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 2beaf13e10..3a2a996192 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -46,6 +46,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.PExpireAt; import static redis_request.RedisRequestOuterClass.RequestType.PTTL; import static redis_request.RedisRequestOuterClass.RequestType.Persist; +import static redis_request.RedisRequestOuterClass.RequestType.PfAdd; import static redis_request.RedisRequestOuterClass.RequestType.Ping; import static redis_request.RedisRequestOuterClass.RequestType.RPop; import static redis_request.RedisRequestOuterClass.RequestType.RPush; @@ -488,6 +489,12 @@ InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3, false), new Limit(1, 2)), .addArgs(WITH_SCORES_REDIS_API) .build())); + transaction.pfadd("hll", new String[] {"a", "b", "c"}); + results.add( + Pair.of( + PfAdd, + ArgsArray.newBuilder().addArgs("hll").addArgs("a").addArgs("b").addArgs("c").build())); + var protobufTransaction = transaction.getProtobufTransaction().build(); for (int idx = 0; idx < protobufTransaction.getCommandsCount(); idx++) { diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index a9c76ba3d7..87c334a98d 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -1434,4 +1434,21 @@ public void zrange_with_different_types_of_keys(BaseClient client) { assertThrows(ExecutionException.class, () -> client.zrangeWithScores(key, query).get()); assertTrue(executionException.getCause() instanceof RequestException); } + + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void pfadd(BaseClient client) { + String key = UUID.randomUUID().toString(); + assertEquals(1, client.pfadd(key, new String[0]).get()); + assertEquals(1, client.pfadd(key, new String[] {"one", "two"}).get()); + assertEquals(0, client.pfadd(key, new String[] {"two"}).get()); + assertEquals(0, client.pfadd(key, new String[0]).get()); + + // Key exists, but it is not a HyperLogLog + assertEquals(OK, client.set("foo", "bar").get()); + ExecutionException executionException = + assertThrows(ExecutionException.class, () -> client.pfadd("foo", new String[0]).get()); + assertTrue(executionException.getCause() instanceof RequestException); + } } diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index 76442dfdab..4cd64cf327 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -19,6 +19,7 @@ public class TransactionTestUtilities { private static final String key6 = "{key}" + UUID.randomUUID(); private static final String key7 = "{key}" + UUID.randomUUID(); private static final String key8 = "{key}" + UUID.randomUUID(); + private static final String hllKey1 = "{key}:hllKey1-" + UUID.randomUUID(); private static final String value1 = UUID.randomUUID().toString(); private static final String value2 = UUID.randomUUID().toString(); private static final String value3 = UUID.randomUUID().toString(); @@ -106,6 +107,8 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransact baseTransaction.echo("GLIDE"); + baseTransaction.pfadd(hllKey1, new String[] {"a", "b", "c"}); + return baseTransaction; } @@ -170,6 +173,7 @@ public static Object[] transactionTestResult() { Map.of("timeout", "1000"), OK, "GLIDE", // echo + 1L, // pfadd(hllKey1, new String[] {"a", "b", "c"}) }; } }