From 29914cb137f7038bbad940f3954f8a421b31b0d6 Mon Sep 17 00:00:00 2001 From: Guian Gumpac Date: Fri, 28 Jun 2024 20:15:01 +0000 Subject: [PATCH 01/39] Java: Add `SSCAN` command (#394) * Add ScanOptions base class for scan-family options. * Expose the cursor as a String to support unsigned 64-bit cursor values. Co-authored-by: James Duong --- .../glide/api/models/BaseTransaction.java | 66 +++++++++++++++++++ .../api/models/commands/scan/ScanOptions.java | 59 +++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 java/client/src/main/java/glide/api/models/commands/scan/ScanOptions.java 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 6dacc98bc4..6c35647718 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -5170,6 +5170,21 @@ public T geosearch( buildArgs(concatenateArrays(new String[] {key}, searchFrom.toArgs(), searchBy.toArgs())); protobufTransaction.addCommands(buildCommand(GeoSearch, args)); return getThis(); + + /** + * Sorts the elements in the list, set, or sorted set at key and returns the result. + *
+ * The sort command can be used to sort elements based on different criteria and + * apply transformations on sorted elements.
+ * To store the result into a new key, see {@link #sortStore(String, String)}.
+ * + * @param key The key of the list, set, or sorted set to be sorted. + * @return Command Response - An Array of sorted elements. + */ + public T sort(@NonNull String key) { + ArgsArray commandArgs = buildArgs(key); + protobufTransaction.addCommands(buildCommand(Sort, commandArgs)); + return getThis(); } /** @@ -5215,6 +5230,22 @@ public T geosearch( return getThis(); } + /** + * Sorts the elements in the list, set, or sorted set at key and returns the result. + *
+ * The sortReadOnly command can be used to sort elements based on different criteria + * and apply transformations on sorted elements. + * + * @since Redis 7.0 and above. + * @param key The key of the list, set, or sorted set to be sorted. + * @return Command Response - An Array of sorted elements. + */ + public T sortReadOnly(@NonNull String key) { + ArgsArray commandArgs = buildArgs(key); + protobufTransaction.addCommands(buildCommand(SortReadOnly, commandArgs)); + return getThis(); + } + /** * Returns the members of a sorted set populated with geospatial information using {@link * #geoadd(String, Map)}, which are within the borders of the area specified by a given shape. @@ -5262,6 +5293,25 @@ public T geosearch( return getThis(); } + /** + * Sorts the elements in the list, set, or sorted set at key and stores the result in + * destination. The sort command can be used to sort elements based on + * different criteria, apply transformations on sorted elements, and store the result in a new + * key.
+ * To get the sort result without storing it into a key, see {@link #sort(String)} or {@link + * #sortReadOnly(String)}. + * + * @param key The key of the list, set, or sorted set to be sorted. + * @param destination The key where the sorted result will be stored. + * @return Command Response - The number of elements in the sorted key stored at destination + * . + */ + public T sortStore(@NonNull String key, @NonNull String destination) { + ArgsArray commandArgs = buildArgs(new String[] {key, STORE_COMMAND_STRING, destination}); + protobufTransaction.addCommands(buildCommand(Sort, commandArgs)); + return getThis(); + } + /** * Returns the members of a sorted set populated with geospatial information using {@link * #geoadd(String, Map)}, which are within the borders of the area specified by a given shape. @@ -5316,6 +5366,22 @@ public T geosearch( return getThis(); } + /** + * Iterates incrementally over a set. + * + * @see valkey.io for details. + * @param key The key of the set. + * @param cursor The cursor that points to the next iteration of results. + * @return Command Response - An Array of Objects. The first element is + * always the cursor for the next iteration of results. 0 will be + * the cursor returned on the last iteration of the set. The second element is + * always an Array of the subset of the set held in key. + */ + public T sscan(@NonNull String key, @NonNull String cursor) { + protobufTransaction.addCommands(buildCommand(SScan, buildArgs(key, cursor))); + return getThis(); + } + /** * Searches for members in a sorted set stored at source representing geospatial data * within a circular or rectangular area and stores the result in destination. If diff --git a/java/client/src/main/java/glide/api/models/commands/scan/ScanOptions.java b/java/client/src/main/java/glide/api/models/commands/scan/ScanOptions.java new file mode 100644 index 0000000000..f02345f5e3 --- /dev/null +++ b/java/client/src/main/java/glide/api/models/commands/scan/ScanOptions.java @@ -0,0 +1,59 @@ +/** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */ +package glide.api.models.commands.scan; + +import java.util.ArrayList; +import java.util.List; +import lombok.experimental.SuperBuilder; + +/** + * This base class represents the common set of optional arguments for the SCAN family of commands. + * Concrete implementations of this class are tied to specific SCAN commands (SCAN, HSCAN, SSCAN, + * and ZSCAN). + */ +@SuperBuilder +public abstract class ScanOptions { + /** MATCH option string to include in the SCAN commands. */ + public static final String MATCH_OPTION_STRING = "MATCH"; + + /** COUNT option string to include in the SCAN commands. */ + public static final String COUNT_OPTION_STRING = "COUNT"; + + /** + * The match filter is applied to the result of the command and will only include strings that + * match the pattern specified. If the set, hash, or list is large enough for scan commands to + * return only a subset of the set, hash, or list, then there could be a case where the result is + * empty although there are items that match the pattern specified. This is due to the default + * COUNT being 10 which indicates that it will only fetch and match + * 10 items from the list. + */ + private final String matchPattern; + + /** + * COUNT is a just a hint for the command for how many elements to fetch from the + * set, hash, or list. COUNT could be ignored until the set, hash, or list is large + * enough for the SCAN commands to represent the results as compact single-allocation + * packed encoding. + */ + private final Long count; + + /** + * Creates the arguments to be used in SCAN commands. + * + * @return a String array that holds the options and their arguments. + */ + public String[] toArgs() { + List optionArgs = new ArrayList<>(); + + if (matchPattern != null) { + optionArgs.add(MATCH_OPTION_STRING); + optionArgs.add(matchPattern); + } + + if (count != null) { + optionArgs.add(COUNT_OPTION_STRING); + optionArgs.add(count.toString()); + } + + return optionArgs.toArray(new String[0]); + } +} From 51f3e35d7879404ade76d330aded010c3f492f60 Mon Sep 17 00:00:00 2001 From: Guian Gumpac Date: Sat, 29 Jun 2024 00:49:10 +0000 Subject: [PATCH 02/39] Java: Add `ZSCAN` command (#397) --------- Co-authored-by: James Duong --- .../src/test/java/glide/api/models/TransactionTests.java | 6 ++++++ 1 file changed, 6 insertions(+) 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 d139b50905..9194c61cb3 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -1370,6 +1370,12 @@ InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3, false), new Limit(1, 2)), ZScanOptions.COUNT_OPTION_STRING, "10"))); + transaction.zscan("key1", "0"); + results.add(Pair.of(ZScan, buildArgs("key1", "0"))); + + transaction.zscan("key1", "0", ZScanOptions.builder().matchPattern("*").count(10L).build()); + results.add(Pair.of(ZScan, buildArgs("key1", "0", "MATCH", "*", "COUNT", "10"))); + var protobufTransaction = transaction.getProtobufTransaction().build(); for (int idx = 0; idx < protobufTransaction.getCommandsCount(); idx++) { From 878facc1da653a62d699c3e6750aabd48773cf0e Mon Sep 17 00:00:00 2001 From: Guian Gumpac Date: Mon, 24 Jun 2024 11:43:52 -0700 Subject: [PATCH 03/39] WIP TODO: support transactions, docs, and more IT --- .../src/main/java/glide/api/BaseClient.java | 1 + .../api/commands/GenericBaseCommands.java | 4 +++ .../api/models/commands/ScanOptions.java | 34 +++++++++++++++++++ .../test/java/glide/api/RedisClientTest.java | 3 ++ .../test/java/glide/SharedCommandTests.java | 1 + 5 files changed, 43 insertions(+) create mode 100644 java/client/src/main/java/glide/api/models/commands/ScanOptions.java diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 463b0564ae..7ed66ac910 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -198,6 +198,7 @@ import glide.api.models.commands.RangeOptions.ScoreRange; import glide.api.models.commands.RangeOptions.ScoredRangeQuery; import glide.api.models.commands.RestoreOptions; +import glide.api.models.commands.ScanOptions; import glide.api.models.commands.ScoreFilter; import glide.api.models.commands.ScriptOptions; import glide.api.models.commands.SetOptions; diff --git a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java index e440b969d7..f62fe8080e 100644 --- a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java @@ -5,6 +5,7 @@ import glide.api.models.Script; import glide.api.models.commands.ExpireOptions; import glide.api.models.commands.RestoreOptions; +import glide.api.models.commands.ScanOptions; import glide.api.models.commands.ScriptOptions; import glide.api.models.configuration.ReadFrom; import java.util.concurrent.CompletableFuture; @@ -1207,4 +1208,7 @@ CompletableFuture restore( * } */ CompletableFuture sortStore(String key, String destination); + CompletableFuture sscan(String key, long cursor); + + CompletableFuture sscan(String key, long cursor, ScanOptions scanOptions); } diff --git a/java/client/src/main/java/glide/api/models/commands/ScanOptions.java b/java/client/src/main/java/glide/api/models/commands/ScanOptions.java new file mode 100644 index 0000000000..cdf157294d --- /dev/null +++ b/java/client/src/main/java/glide/api/models/commands/ScanOptions.java @@ -0,0 +1,34 @@ +/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ +package glide.api.models.commands; + +import java.util.ArrayList; +import java.util.List; +import lombok.Builder; + +@Builder +public class ScanOptions { + /** MATCH option string to include in the SCAN commands. */ + public static final String MATCH_OPTION_STRING = "MATCH"; + + /** COUNT option string to include in the SCAN commands. */ + public static final String COUNT_OPTION_STRING = "COUNT"; + + private final String matchPattern; + private final Long count; + + public String[] toArgs() { + List optionArgs = new ArrayList<>(); + + if (matchPattern != null) { + optionArgs.add(MATCH_OPTION_STRING); + optionArgs.add(matchPattern); + } + + if (count != null) { + optionArgs.add(COUNT_OPTION_STRING); + optionArgs.add(count.toString()); + } + + 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 1eabe0776f..cbdaf5ffc8 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -21,6 +21,8 @@ import static glide.api.models.commands.FlushMode.ASYNC; import static glide.api.models.commands.FlushMode.SYNC; import static glide.api.models.commands.LInsertOptions.InsertPosition.BEFORE; +import static glide.api.models.commands.ScanOptions.COUNT_OPTION_STRING; +import static glide.api.models.commands.ScanOptions.MATCH_OPTION_STRING; import static glide.api.models.commands.ScoreFilter.MAX; import static glide.api.models.commands.SetOptions.ConditionalSet.ONLY_IF_DOES_NOT_EXIST; import static glide.api.models.commands.SetOptions.ConditionalSet.ONLY_IF_EXISTS; @@ -277,6 +279,7 @@ import glide.api.models.commands.RangeOptions.RangeByScore; import glide.api.models.commands.RangeOptions.ScoreBoundary; import glide.api.models.commands.RestoreOptions; +import glide.api.models.commands.ScanOptions; import glide.api.models.commands.ScoreFilter; import glide.api.models.commands.ScriptOptions; import glide.api.models.commands.SetOptions; diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 74dbac710b..3981076ca4 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -48,6 +48,7 @@ import glide.api.models.commands.RangeOptions.RangeByScore; import glide.api.models.commands.RangeOptions.ScoreBoundary; import glide.api.models.commands.RestoreOptions; +import glide.api.models.commands.ScanOptions; import glide.api.models.commands.ScriptOptions; import glide.api.models.commands.SetOptions; import glide.api.models.commands.SortOrder; From f028224c9cf37905a4b0dfac89b5a74fbdadc742 Mon Sep 17 00:00:00 2001 From: Guian Gumpac Date: Tue, 25 Jun 2024 07:16:48 -0700 Subject: [PATCH 04/39] Added more tests --- java/integTest/src/test/java/glide/SharedCommandTests.java | 1 + 1 file changed, 1 insertion(+) diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 3981076ca4..b1af9a9bc9 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -118,6 +118,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.opentest4j.AssertionFailedError; @Timeout(10) // seconds public class SharedCommandTests { From 79ce6fd0d6a89b092bfc7f9e49385b022b227cd7 Mon Sep 17 00:00:00 2001 From: Guian Gumpac Date: Wed, 26 Jun 2024 18:32:02 -0700 Subject: [PATCH 05/39] Added tests and javadocs --- .../src/main/java/glide/api/BaseClient.java | 1 - .../api/commands/GenericBaseCommands.java | 1 - .../api/models/commands/ScanOptions.java | 34 ------------------- .../test/java/glide/api/RedisClientTest.java | 3 -- .../test/java/glide/SharedCommandTests.java | 2 -- .../java/glide/TransactionTestUtilities.java | 4 +++ 6 files changed, 4 insertions(+), 41 deletions(-) delete mode 100644 java/client/src/main/java/glide/api/models/commands/ScanOptions.java diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 7ed66ac910..463b0564ae 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -198,7 +198,6 @@ import glide.api.models.commands.RangeOptions.ScoreRange; import glide.api.models.commands.RangeOptions.ScoredRangeQuery; import glide.api.models.commands.RestoreOptions; -import glide.api.models.commands.ScanOptions; import glide.api.models.commands.ScoreFilter; import glide.api.models.commands.ScriptOptions; import glide.api.models.commands.SetOptions; diff --git a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java index f62fe8080e..0afcb9084a 100644 --- a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java @@ -5,7 +5,6 @@ import glide.api.models.Script; import glide.api.models.commands.ExpireOptions; import glide.api.models.commands.RestoreOptions; -import glide.api.models.commands.ScanOptions; import glide.api.models.commands.ScriptOptions; import glide.api.models.configuration.ReadFrom; import java.util.concurrent.CompletableFuture; diff --git a/java/client/src/main/java/glide/api/models/commands/ScanOptions.java b/java/client/src/main/java/glide/api/models/commands/ScanOptions.java deleted file mode 100644 index cdf157294d..0000000000 --- a/java/client/src/main/java/glide/api/models/commands/ScanOptions.java +++ /dev/null @@ -1,34 +0,0 @@ -/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ -package glide.api.models.commands; - -import java.util.ArrayList; -import java.util.List; -import lombok.Builder; - -@Builder -public class ScanOptions { - /** MATCH option string to include in the SCAN commands. */ - public static final String MATCH_OPTION_STRING = "MATCH"; - - /** COUNT option string to include in the SCAN commands. */ - public static final String COUNT_OPTION_STRING = "COUNT"; - - private final String matchPattern; - private final Long count; - - public String[] toArgs() { - List optionArgs = new ArrayList<>(); - - if (matchPattern != null) { - optionArgs.add(MATCH_OPTION_STRING); - optionArgs.add(matchPattern); - } - - if (count != null) { - optionArgs.add(COUNT_OPTION_STRING); - optionArgs.add(count.toString()); - } - - 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 cbdaf5ffc8..1eabe0776f 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -21,8 +21,6 @@ import static glide.api.models.commands.FlushMode.ASYNC; import static glide.api.models.commands.FlushMode.SYNC; import static glide.api.models.commands.LInsertOptions.InsertPosition.BEFORE; -import static glide.api.models.commands.ScanOptions.COUNT_OPTION_STRING; -import static glide.api.models.commands.ScanOptions.MATCH_OPTION_STRING; import static glide.api.models.commands.ScoreFilter.MAX; import static glide.api.models.commands.SetOptions.ConditionalSet.ONLY_IF_DOES_NOT_EXIST; import static glide.api.models.commands.SetOptions.ConditionalSet.ONLY_IF_EXISTS; @@ -279,7 +277,6 @@ import glide.api.models.commands.RangeOptions.RangeByScore; import glide.api.models.commands.RangeOptions.ScoreBoundary; import glide.api.models.commands.RestoreOptions; -import glide.api.models.commands.ScanOptions; import glide.api.models.commands.ScoreFilter; import glide.api.models.commands.ScriptOptions; import glide.api.models.commands.SetOptions; diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index b1af9a9bc9..74dbac710b 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -48,7 +48,6 @@ import glide.api.models.commands.RangeOptions.RangeByScore; import glide.api.models.commands.RangeOptions.ScoreBoundary; import glide.api.models.commands.RestoreOptions; -import glide.api.models.commands.ScanOptions; import glide.api.models.commands.ScriptOptions; import glide.api.models.commands.SetOptions; import glide.api.models.commands.SortOrder; @@ -118,7 +117,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import org.opentest4j.AssertionFailedError; @Timeout(10) // seconds public class SharedCommandTests { diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index f0797b2db8..949eee305d 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -527,6 +527,8 @@ private static Object[] setCommands(BaseTransaction transaction) { transaction .sadd(setKey1, new String[] {"baz", "foo"}) + .sscan(setKey1, 0) + .sscan(setKey1, 0, SScanOptions.builder().matchPattern("*").count(10L).build()) .srem(setKey1, new String[] {"foo"}) .sscan(setKey1, "0") .sscan(setKey1, "0", SScanOptions.builder().matchPattern("*").count(10L).build()) @@ -560,6 +562,8 @@ private static Object[] setCommands(BaseTransaction transaction) { var expectedResults = new Object[] { 2L, // sadd(setKey1, new String[] {"baz", "foo"}); + new Object[] {"0", new String[] {"baz", "foo"}}, // sscan(setKey1, 0) + new Object[] {"0", new String[] {"baz", "foo"}}, // sscan(key1, 0, match "*", count(10L)) 1L, // srem(setKey1, new String[] {"foo"}); new Object[] {"0", new String[] {"baz"}}, // sscan(setKey1, "0") new Object[] {"0", new String[] {"baz"}}, // sscan(key1, "0", match "*", count(10L)) From 9f76d08de29a0d90688b65092e5f03a499b05b99 Mon Sep 17 00:00:00 2001 From: Guian Gumpac Date: Wed, 26 Jun 2024 21:29:37 -0700 Subject: [PATCH 06/39] Improved examples and tests --- .../src/test/java/glide/TransactionTestUtilities.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index 949eee305d..bb32fe7f02 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -527,6 +527,7 @@ private static Object[] setCommands(BaseTransaction transaction) { transaction .sadd(setKey1, new String[] {"baz", "foo"}) + .srem(setKey1, new String[] {"foo"}) .sscan(setKey1, 0) .sscan(setKey1, 0, SScanOptions.builder().matchPattern("*").count(10L).build()) .srem(setKey1, new String[] {"foo"}) @@ -562,8 +563,6 @@ private static Object[] setCommands(BaseTransaction transaction) { var expectedResults = new Object[] { 2L, // sadd(setKey1, new String[] {"baz", "foo"}); - new Object[] {"0", new String[] {"baz", "foo"}}, // sscan(setKey1, 0) - new Object[] {"0", new String[] {"baz", "foo"}}, // sscan(key1, 0, match "*", count(10L)) 1L, // srem(setKey1, new String[] {"foo"}); new Object[] {"0", new String[] {"baz"}}, // sscan(setKey1, "0") new Object[] {"0", new String[] {"baz"}}, // sscan(key1, "0", match "*", count(10L)) From 0f694774f3ac415261e9994eaf10b52d20d2f267 Mon Sep 17 00:00:00 2001 From: James Duong Date: Thu, 27 Jun 2024 12:59:15 -0700 Subject: [PATCH 07/39] Correct use of SScanOptions instead of ScanOptions for SScan --- .../src/main/java/glide/api/commands/GenericBaseCommands.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java index 0afcb9084a..6a43e72e8a 100644 --- a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java @@ -6,6 +6,7 @@ import glide.api.models.commands.ExpireOptions; import glide.api.models.commands.RestoreOptions; import glide.api.models.commands.ScriptOptions; +import glide.api.models.commands.scan.SScanOptions; import glide.api.models.configuration.ReadFrom; import java.util.concurrent.CompletableFuture; @@ -1209,5 +1210,5 @@ CompletableFuture restore( CompletableFuture sortStore(String key, String destination); CompletableFuture sscan(String key, long cursor); - CompletableFuture sscan(String key, long cursor, ScanOptions scanOptions); + CompletableFuture sscan(String key, long cursor, SScanOptions scanOptions); } From 343c7ad0a05de6267e0daaa28b4fcd1378296010 Mon Sep 17 00:00:00 2001 From: James Duong Date: Thu, 27 Jun 2024 13:31:34 -0700 Subject: [PATCH 08/39] Remove plumbing for SCAN command --- .../src/main/java/glide/api/commands/GenericBaseCommands.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java index 6a43e72e8a..e440b969d7 100644 --- a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java @@ -6,7 +6,6 @@ import glide.api.models.commands.ExpireOptions; import glide.api.models.commands.RestoreOptions; import glide.api.models.commands.ScriptOptions; -import glide.api.models.commands.scan.SScanOptions; import glide.api.models.configuration.ReadFrom; import java.util.concurrent.CompletableFuture; @@ -1208,7 +1207,4 @@ CompletableFuture restore( * } */ CompletableFuture sortStore(String key, String destination); - CompletableFuture sscan(String key, long cursor); - - CompletableFuture sscan(String key, long cursor, SScanOptions scanOptions); } From 851d952ecc8fc1618b2577dcbba11f66236ae9c9 Mon Sep 17 00:00:00 2001 From: James Duong Date: Thu, 27 Jun 2024 22:28:27 -0700 Subject: [PATCH 09/39] Sleep after sadd() calls before sscan() calls Due to eventual consistency --- java/integTest/src/test/java/glide/SharedCommandTests.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 74dbac710b..6fc2ec8c7c 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -7621,6 +7621,12 @@ public void sscan(BaseClient client) { // Result contains the whole set assertEquals(charMembers.length, client.sadd(key1, charMembers).get()); + // Sleep after sadd() for eventual consistency. + // TODO: Replace sleep with WAIT request to enforce strong consistency. + try { + Thread.sleep(1000); + } catch (InterruptedException ex) { + } result = client.sscan(key1, initialCursor).get(); assertEquals(initialCursor, result[resultCursorIndex]); assertEquals(charMembers.length, ((Object[]) result[resultCollectionIndex]).length); From c708547765859816a4dae2233774d5b4bb919787 Mon Sep 17 00:00:00 2001 From: James Duong Date: Fri, 28 Jun 2024 01:16:30 -0700 Subject: [PATCH 10/39] Change sscan cursor to be a String Also fix bug in SharedCommandTests --- java/integTest/src/test/java/glide/SharedCommandTests.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 6fc2ec8c7c..74dbac710b 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -7621,12 +7621,6 @@ public void sscan(BaseClient client) { // Result contains the whole set assertEquals(charMembers.length, client.sadd(key1, charMembers).get()); - // Sleep after sadd() for eventual consistency. - // TODO: Replace sleep with WAIT request to enforce strong consistency. - try { - Thread.sleep(1000); - } catch (InterruptedException ex) { - } result = client.sscan(key1, initialCursor).get(); assertEquals(initialCursor, result[resultCursorIndex]); assertEquals(charMembers.length, ((Object[]) result[resultCollectionIndex]).length); From 450e17d9657611fe7aa1f18c884644d4a3ac64f8 Mon Sep 17 00:00:00 2001 From: Guian Gumpac Date: Thu, 27 Jun 2024 09:31:23 -0700 Subject: [PATCH 11/39] WIP with todos # Conflicts: # glide-core/src/protobuf/redis_request.proto # glide-core/src/request_type.rs # java/client/src/main/java/glide/api/commands/SortedSetBaseCommands.java --- java/client/src/test/java/glide/api/RedisClientTest.java | 1 + java/integTest/src/test/java/glide/TransactionTestUtilities.java | 1 + 2 files changed, 2 insertions(+) diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 1eabe0776f..5a8122dafa 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -325,6 +325,7 @@ import glide.managers.CommandManager; import glide.managers.ConnectionManager; import java.util.ArrayList; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index bb32fe7f02..f220700978 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -637,6 +637,7 @@ private static Object[] sortedSetCommands(BaseTransaction transaction) { .zscan(zSetKey2, "0") .zscan(zSetKey2, "0", ZScanOptions.builder().count(20L).build()) .bzpopmin(new String[] {zSetKey2}, .1); + // TODO: Add tests for zscan // zSetKey2 is now empty if (REDIS_VERSION.isGreaterThanOrEqualTo("6.2.0")) { From da923a43f8413c2dc72ef2d6e7333433d728a562 Mon Sep 17 00:00:00 2001 From: James Duong Date: Thu, 27 Jun 2024 12:33:57 -0700 Subject: [PATCH 12/39] Add ZScan to TransactionTestUtilities --- java/integTest/src/test/java/glide/TransactionTestUtilities.java | 1 - 1 file changed, 1 deletion(-) diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index f220700978..bb32fe7f02 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -637,7 +637,6 @@ private static Object[] sortedSetCommands(BaseTransaction transaction) { .zscan(zSetKey2, "0") .zscan(zSetKey2, "0", ZScanOptions.builder().count(20L).build()) .bzpopmin(new String[] {zSetKey2}, .1); - // TODO: Add tests for zscan // zSetKey2 is now empty if (REDIS_VERSION.isGreaterThanOrEqualTo("6.2.0")) { From 01c0794a90a3d734f10c7ccf4b3dcbbdd105fa22 Mon Sep 17 00:00:00 2001 From: James Duong Date: Thu, 27 Jun 2024 14:13:39 -0700 Subject: [PATCH 13/39] Spotless cleanup --- java/client/src/test/java/glide/api/RedisClientTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 5a8122dafa..1eabe0776f 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -325,7 +325,6 @@ import glide.managers.CommandManager; import glide.managers.ConnectionManager; import java.util.ArrayList; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; From ae014e57d678473e2a897626f031da15be9e53b8 Mon Sep 17 00:00:00 2001 From: James Duong Date: Thu, 27 Jun 2024 17:24:35 -0700 Subject: [PATCH 14/39] Test fixes --- java/integTest/src/test/java/glide/SharedCommandTests.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 74dbac710b..e07e7db020 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -7857,6 +7857,10 @@ public void zscan(BaseClient client) { "secondResultAllValues: {%s} numberMapValuesAsStrings: {%s}", secondResultAllValues, numberMapValuesAsStrings)); + assertTrue(secondResultAllKeys.containsAll(numberMap.keySet())); + assertTrue(secondResultAllValues.containsAll( + numberMap.values().stream().map(d -> "" + d.intValue()).collect(Collectors.toSet()))); + // Test match pattern result = client.zscan(key1, initialCursor, ZScanOptions.builder().matchPattern("*").build()).get(); From e65a9f7460a484652b6e2b2981851ff171737411 Mon Sep 17 00:00:00 2001 From: James Duong Date: Thu, 27 Jun 2024 18:05:30 -0700 Subject: [PATCH 15/39] Cleanup test code * Apply IntelliJ suggestions * Use String.valueOf() instead of concatenating empty string --- java/integTest/src/test/java/glide/SharedCommandTests.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index e07e7db020..cd96cc8cd7 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -7858,8 +7858,11 @@ public void zscan(BaseClient client) { secondResultAllValues, numberMapValuesAsStrings)); assertTrue(secondResultAllKeys.containsAll(numberMap.keySet())); - assertTrue(secondResultAllValues.containsAll( - numberMap.values().stream().map(d -> "" + d.intValue()).collect(Collectors.toSet()))); + assertTrue( + secondResultAllValues.containsAll( + numberMap.values().stream() + .map(d -> String.valueOf(d.intValue())) + .collect(Collectors.toSet()))); // Test match pattern result = From 90cad597cf8a45e8ccc7bc12ae5ea2b68b48855b Mon Sep 17 00:00:00 2001 From: James Duong Date: Thu, 27 Jun 2024 20:46:19 -0700 Subject: [PATCH 16/39] Added better error info for set comparison failures --- .../src/test/java/glide/SharedCommandTests.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index cd96cc8cd7..3021c664ac 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -7858,11 +7858,16 @@ public void zscan(BaseClient client) { secondResultAllValues, numberMapValuesAsStrings)); assertTrue(secondResultAllKeys.containsAll(numberMap.keySet())); + final Set numberMapValuesAsStrings = + numberMap.values().stream() + .map(d -> String.valueOf(d.intValue())) + .collect(Collectors.toSet()); + assertTrue( - secondResultAllValues.containsAll( - numberMap.values().stream() - .map(d -> String.valueOf(d.intValue())) - .collect(Collectors.toSet()))); + secondResultAllValues.containsAll(numberMapValuesAsStrings), + String.format( + "secondResultAllValues: {%s} numberMapValuesAsStrings: {%s}", + secondResultAllValues, numberMapValuesAsStrings)); // Test match pattern result = From 213a873178eaa55c3d704f84149f72d643343330 Mon Sep 17 00:00:00 2001 From: James Duong Date: Thu, 27 Jun 2024 21:38:23 -0700 Subject: [PATCH 17/39] More logging for test failures --- java/integTest/src/test/java/glide/SharedCommandTests.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 3021c664ac..7c3afc4221 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -7857,7 +7857,12 @@ public void zscan(BaseClient client) { "secondResultAllValues: {%s} numberMapValuesAsStrings: {%s}", secondResultAllValues, numberMapValuesAsStrings)); - assertTrue(secondResultAllKeys.containsAll(numberMap.keySet())); + assertTrue( + secondResultAllKeys.containsAll(numberMap.keySet()), + String.format( + "secondResultAllKeys: {%s} numberMap.keySet: {%s}", + secondResultAllKeys, numberMap.keySet())); + final Set numberMapValuesAsStrings = numberMap.values().stream() .map(d -> String.valueOf(d.intValue())) From bb49b585b8b9052525c3f54d18527ef928a22f3a Mon Sep 17 00:00:00 2001 From: James Duong Date: Thu, 27 Jun 2024 22:25:53 -0700 Subject: [PATCH 18/39] Add sleeps after zadd() calls To help make sure data is consistent without WAIT --- java/integTest/src/test/java/glide/SharedCommandTests.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 7c3afc4221..ca0be4f032 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -7766,6 +7766,12 @@ public void zscan(BaseClient client) { // Result contains the whole set assertEquals(charMembers.length, client.zadd(key1, charMap).get()); + // Sleep after zadd() for eventual consistency. + // TODO: Replace sleep with WAIT request to enforce strong consistency. + try { + Thread.sleep(1000); + } catch (InterruptedException ex) { + } result = client.zscan(key1, initialCursor).get(); assertEquals(initialCursor, result[resultCursorIndex]); assertEquals( From e1bed7cbfceef887d3d317491fbaaa3ed5ce82cb Mon Sep 17 00:00:00 2001 From: James Duong Date: Thu, 27 Jun 2024 23:10:10 -0700 Subject: [PATCH 19/39] Longer sleeps --- java/integTest/src/test/java/glide/SharedCommandTests.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index ca0be4f032..4794973bf1 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -7768,10 +7768,7 @@ public void zscan(BaseClient client) { assertEquals(charMembers.length, client.zadd(key1, charMap).get()); // Sleep after zadd() for eventual consistency. // TODO: Replace sleep with WAIT request to enforce strong consistency. - try { - Thread.sleep(1000); - } catch (InterruptedException ex) { - } + Thread.sleep(5000); result = client.zscan(key1, initialCursor).get(); assertEquals(initialCursor, result[resultCursorIndex]); assertEquals( From 3406cd8c1b2c67014f839eaeb4c9e4af1094b9cd Mon Sep 17 00:00:00 2001 From: James Duong Date: Thu, 27 Jun 2024 23:21:44 -0700 Subject: [PATCH 20/39] Reduce wait time --- java/integTest/src/test/java/glide/SharedCommandTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 4794973bf1..5ef42d9782 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -7768,7 +7768,7 @@ public void zscan(BaseClient client) { assertEquals(charMembers.length, client.zadd(key1, charMap).get()); // Sleep after zadd() for eventual consistency. // TODO: Replace sleep with WAIT request to enforce strong consistency. - Thread.sleep(5000); + Thread.sleep(4200); result = client.zscan(key1, initialCursor).get(); assertEquals(initialCursor, result[resultCursorIndex]); assertEquals( From b908a0b7438a8166bbfd18348344bac6ca3a80df Mon Sep 17 00:00:00 2001 From: James Duong Date: Fri, 28 Jun 2024 00:04:36 -0700 Subject: [PATCH 21/39] Experiment with unsigned 64-bit cursors --- java/integTest/src/test/java/glide/SharedCommandTests.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 5ef42d9782..7c3afc4221 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -7766,9 +7766,6 @@ public void zscan(BaseClient client) { // Result contains the whole set assertEquals(charMembers.length, client.zadd(key1, charMap).get()); - // Sleep after zadd() for eventual consistency. - // TODO: Replace sleep with WAIT request to enforce strong consistency. - Thread.sleep(4200); result = client.zscan(key1, initialCursor).get(); assertEquals(initialCursor, result[resultCursorIndex]); assertEquals( From 7ba31b6d808319d1b228ff17fe27b57ae50cb2d4 Mon Sep 17 00:00:00 2001 From: James Duong Date: Fri, 28 Jun 2024 16:35:48 -0700 Subject: [PATCH 22/39] Fix rebase error --- .../src/test/java/glide/TransactionTestUtilities.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index bb32fe7f02..f0797b2db8 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -528,9 +528,6 @@ private static Object[] setCommands(BaseTransaction transaction) { transaction .sadd(setKey1, new String[] {"baz", "foo"}) .srem(setKey1, new String[] {"foo"}) - .sscan(setKey1, 0) - .sscan(setKey1, 0, SScanOptions.builder().matchPattern("*").count(10L).build()) - .srem(setKey1, new String[] {"foo"}) .sscan(setKey1, "0") .sscan(setKey1, "0", SScanOptions.builder().matchPattern("*").count(10L).build()) .scard(setKey1) From cf86bfdb182fede9fde8027c1d62f873f9ee217e Mon Sep 17 00:00:00 2001 From: Guian Gumpac Date: Mon, 24 Jun 2024 11:43:52 -0700 Subject: [PATCH 23/39] WIP TODO: support transactions, docs, and more IT --- .../src/main/java/glide/api/BaseClient.java | 1 + .../api/commands/GenericBaseCommands.java | 4 +++ .../api/models/commands/ScanOptions.java | 34 +++++++++++++++++++ .../test/java/glide/api/RedisClientTest.java | 3 ++ .../test/java/glide/SharedCommandTests.java | 1 + 5 files changed, 43 insertions(+) create mode 100644 java/client/src/main/java/glide/api/models/commands/ScanOptions.java diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 463b0564ae..7ed66ac910 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -198,6 +198,7 @@ import glide.api.models.commands.RangeOptions.ScoreRange; import glide.api.models.commands.RangeOptions.ScoredRangeQuery; import glide.api.models.commands.RestoreOptions; +import glide.api.models.commands.ScanOptions; import glide.api.models.commands.ScoreFilter; import glide.api.models.commands.ScriptOptions; import glide.api.models.commands.SetOptions; diff --git a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java index e440b969d7..f62fe8080e 100644 --- a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java @@ -5,6 +5,7 @@ import glide.api.models.Script; import glide.api.models.commands.ExpireOptions; import glide.api.models.commands.RestoreOptions; +import glide.api.models.commands.ScanOptions; import glide.api.models.commands.ScriptOptions; import glide.api.models.configuration.ReadFrom; import java.util.concurrent.CompletableFuture; @@ -1207,4 +1208,7 @@ CompletableFuture restore( * } */ CompletableFuture sortStore(String key, String destination); + CompletableFuture sscan(String key, long cursor); + + CompletableFuture sscan(String key, long cursor, ScanOptions scanOptions); } diff --git a/java/client/src/main/java/glide/api/models/commands/ScanOptions.java b/java/client/src/main/java/glide/api/models/commands/ScanOptions.java new file mode 100644 index 0000000000..cdf157294d --- /dev/null +++ b/java/client/src/main/java/glide/api/models/commands/ScanOptions.java @@ -0,0 +1,34 @@ +/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ +package glide.api.models.commands; + +import java.util.ArrayList; +import java.util.List; +import lombok.Builder; + +@Builder +public class ScanOptions { + /** MATCH option string to include in the SCAN commands. */ + public static final String MATCH_OPTION_STRING = "MATCH"; + + /** COUNT option string to include in the SCAN commands. */ + public static final String COUNT_OPTION_STRING = "COUNT"; + + private final String matchPattern; + private final Long count; + + public String[] toArgs() { + List optionArgs = new ArrayList<>(); + + if (matchPattern != null) { + optionArgs.add(MATCH_OPTION_STRING); + optionArgs.add(matchPattern); + } + + if (count != null) { + optionArgs.add(COUNT_OPTION_STRING); + optionArgs.add(count.toString()); + } + + 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 1eabe0776f..cbdaf5ffc8 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -21,6 +21,8 @@ import static glide.api.models.commands.FlushMode.ASYNC; import static glide.api.models.commands.FlushMode.SYNC; import static glide.api.models.commands.LInsertOptions.InsertPosition.BEFORE; +import static glide.api.models.commands.ScanOptions.COUNT_OPTION_STRING; +import static glide.api.models.commands.ScanOptions.MATCH_OPTION_STRING; import static glide.api.models.commands.ScoreFilter.MAX; import static glide.api.models.commands.SetOptions.ConditionalSet.ONLY_IF_DOES_NOT_EXIST; import static glide.api.models.commands.SetOptions.ConditionalSet.ONLY_IF_EXISTS; @@ -277,6 +279,7 @@ import glide.api.models.commands.RangeOptions.RangeByScore; import glide.api.models.commands.RangeOptions.ScoreBoundary; import glide.api.models.commands.RestoreOptions; +import glide.api.models.commands.ScanOptions; import glide.api.models.commands.ScoreFilter; import glide.api.models.commands.ScriptOptions; import glide.api.models.commands.SetOptions; diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 7c3afc4221..e1aecbaa73 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -48,6 +48,7 @@ import glide.api.models.commands.RangeOptions.RangeByScore; import glide.api.models.commands.RangeOptions.ScoreBoundary; import glide.api.models.commands.RestoreOptions; +import glide.api.models.commands.ScanOptions; import glide.api.models.commands.ScriptOptions; import glide.api.models.commands.SetOptions; import glide.api.models.commands.SortOrder; From 1de0e9d34d8351482725acbdaf964bc3fb212179 Mon Sep 17 00:00:00 2001 From: Guian Gumpac Date: Tue, 25 Jun 2024 07:16:48 -0700 Subject: [PATCH 24/39] Added more tests --- java/integTest/src/test/java/glide/SharedCommandTests.java | 1 + 1 file changed, 1 insertion(+) diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index e1aecbaa73..0e3e6e090c 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -118,6 +118,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.opentest4j.AssertionFailedError; @Timeout(10) // seconds public class SharedCommandTests { From 43764ec8d886e10abd34c32bfa3ea424f66a1439 Mon Sep 17 00:00:00 2001 From: Guian Gumpac Date: Wed, 26 Jun 2024 18:32:02 -0700 Subject: [PATCH 25/39] Added tests and javadocs --- .../src/main/java/glide/api/BaseClient.java | 1 - .../api/commands/GenericBaseCommands.java | 1 - .../api/models/commands/ScanOptions.java | 34 ------------------- .../test/java/glide/api/RedisClientTest.java | 3 -- .../test/java/glide/SharedCommandTests.java | 2 -- .../java/glide/TransactionTestUtilities.java | 4 +++ 6 files changed, 4 insertions(+), 41 deletions(-) delete mode 100644 java/client/src/main/java/glide/api/models/commands/ScanOptions.java diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 7ed66ac910..463b0564ae 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -198,7 +198,6 @@ import glide.api.models.commands.RangeOptions.ScoreRange; import glide.api.models.commands.RangeOptions.ScoredRangeQuery; import glide.api.models.commands.RestoreOptions; -import glide.api.models.commands.ScanOptions; import glide.api.models.commands.ScoreFilter; import glide.api.models.commands.ScriptOptions; import glide.api.models.commands.SetOptions; diff --git a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java index f62fe8080e..0afcb9084a 100644 --- a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java @@ -5,7 +5,6 @@ import glide.api.models.Script; import glide.api.models.commands.ExpireOptions; import glide.api.models.commands.RestoreOptions; -import glide.api.models.commands.ScanOptions; import glide.api.models.commands.ScriptOptions; import glide.api.models.configuration.ReadFrom; import java.util.concurrent.CompletableFuture; diff --git a/java/client/src/main/java/glide/api/models/commands/ScanOptions.java b/java/client/src/main/java/glide/api/models/commands/ScanOptions.java deleted file mode 100644 index cdf157294d..0000000000 --- a/java/client/src/main/java/glide/api/models/commands/ScanOptions.java +++ /dev/null @@ -1,34 +0,0 @@ -/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ -package glide.api.models.commands; - -import java.util.ArrayList; -import java.util.List; -import lombok.Builder; - -@Builder -public class ScanOptions { - /** MATCH option string to include in the SCAN commands. */ - public static final String MATCH_OPTION_STRING = "MATCH"; - - /** COUNT option string to include in the SCAN commands. */ - public static final String COUNT_OPTION_STRING = "COUNT"; - - private final String matchPattern; - private final Long count; - - public String[] toArgs() { - List optionArgs = new ArrayList<>(); - - if (matchPattern != null) { - optionArgs.add(MATCH_OPTION_STRING); - optionArgs.add(matchPattern); - } - - if (count != null) { - optionArgs.add(COUNT_OPTION_STRING); - optionArgs.add(count.toString()); - } - - 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 cbdaf5ffc8..1eabe0776f 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -21,8 +21,6 @@ import static glide.api.models.commands.FlushMode.ASYNC; import static glide.api.models.commands.FlushMode.SYNC; import static glide.api.models.commands.LInsertOptions.InsertPosition.BEFORE; -import static glide.api.models.commands.ScanOptions.COUNT_OPTION_STRING; -import static glide.api.models.commands.ScanOptions.MATCH_OPTION_STRING; import static glide.api.models.commands.ScoreFilter.MAX; import static glide.api.models.commands.SetOptions.ConditionalSet.ONLY_IF_DOES_NOT_EXIST; import static glide.api.models.commands.SetOptions.ConditionalSet.ONLY_IF_EXISTS; @@ -279,7 +277,6 @@ import glide.api.models.commands.RangeOptions.RangeByScore; import glide.api.models.commands.RangeOptions.ScoreBoundary; import glide.api.models.commands.RestoreOptions; -import glide.api.models.commands.ScanOptions; import glide.api.models.commands.ScoreFilter; import glide.api.models.commands.ScriptOptions; import glide.api.models.commands.SetOptions; diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 0e3e6e090c..7c3afc4221 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -48,7 +48,6 @@ import glide.api.models.commands.RangeOptions.RangeByScore; import glide.api.models.commands.RangeOptions.ScoreBoundary; import glide.api.models.commands.RestoreOptions; -import glide.api.models.commands.ScanOptions; import glide.api.models.commands.ScriptOptions; import glide.api.models.commands.SetOptions; import glide.api.models.commands.SortOrder; @@ -118,7 +117,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import org.opentest4j.AssertionFailedError; @Timeout(10) // seconds public class SharedCommandTests { diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index f0797b2db8..949eee305d 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -527,6 +527,8 @@ private static Object[] setCommands(BaseTransaction transaction) { transaction .sadd(setKey1, new String[] {"baz", "foo"}) + .sscan(setKey1, 0) + .sscan(setKey1, 0, SScanOptions.builder().matchPattern("*").count(10L).build()) .srem(setKey1, new String[] {"foo"}) .sscan(setKey1, "0") .sscan(setKey1, "0", SScanOptions.builder().matchPattern("*").count(10L).build()) @@ -560,6 +562,8 @@ private static Object[] setCommands(BaseTransaction transaction) { var expectedResults = new Object[] { 2L, // sadd(setKey1, new String[] {"baz", "foo"}); + new Object[] {"0", new String[] {"baz", "foo"}}, // sscan(setKey1, 0) + new Object[] {"0", new String[] {"baz", "foo"}}, // sscan(key1, 0, match "*", count(10L)) 1L, // srem(setKey1, new String[] {"foo"}); new Object[] {"0", new String[] {"baz"}}, // sscan(setKey1, "0") new Object[] {"0", new String[] {"baz"}}, // sscan(key1, "0", match "*", count(10L)) From fe4162f8994884e3923a61189f9c26ea159f47fa Mon Sep 17 00:00:00 2001 From: Guian Gumpac Date: Wed, 26 Jun 2024 21:29:37 -0700 Subject: [PATCH 26/39] Improved examples and tests --- .../src/test/java/glide/TransactionTestUtilities.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index 949eee305d..4373280600 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -527,8 +527,7 @@ private static Object[] setCommands(BaseTransaction transaction) { transaction .sadd(setKey1, new String[] {"baz", "foo"}) - .sscan(setKey1, 0) - .sscan(setKey1, 0, SScanOptions.builder().matchPattern("*").count(10L).build()) + .srem(setKey1, new String[] {"foo"}) .sscan(setKey1, "0") .sscan(setKey1, "0", SScanOptions.builder().matchPattern("*").count(10L).build()) @@ -562,8 +561,6 @@ private static Object[] setCommands(BaseTransaction transaction) { var expectedResults = new Object[] { 2L, // sadd(setKey1, new String[] {"baz", "foo"}); - new Object[] {"0", new String[] {"baz", "foo"}}, // sscan(setKey1, 0) - new Object[] {"0", new String[] {"baz", "foo"}}, // sscan(key1, 0, match "*", count(10L)) 1L, // srem(setKey1, new String[] {"foo"}); new Object[] {"0", new String[] {"baz"}}, // sscan(setKey1, "0") new Object[] {"0", new String[] {"baz"}}, // sscan(key1, "0", match "*", count(10L)) From 2f731653bdd2a3556613fcd6e3d5a82234f80844 Mon Sep 17 00:00:00 2001 From: James Duong Date: Thu, 27 Jun 2024 22:28:27 -0700 Subject: [PATCH 27/39] Sleep after sadd() calls before sscan() calls Due to eventual consistency --- java/integTest/src/test/java/glide/SharedCommandTests.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 7c3afc4221..19979f24fa 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -7621,6 +7621,12 @@ public void sscan(BaseClient client) { // Result contains the whole set assertEquals(charMembers.length, client.sadd(key1, charMembers).get()); + // Sleep after sadd() for eventual consistency. + // TODO: Replace sleep with WAIT request to enforce strong consistency. + try { + Thread.sleep(1000); + } catch (InterruptedException ex) { + } result = client.sscan(key1, initialCursor).get(); assertEquals(initialCursor, result[resultCursorIndex]); assertEquals(charMembers.length, ((Object[]) result[resultCollectionIndex]).length); From fc028cd7d0bf26e4cd83f34d8328b00c6f3f0b02 Mon Sep 17 00:00:00 2001 From: James Duong Date: Fri, 28 Jun 2024 01:16:30 -0700 Subject: [PATCH 28/39] Change sscan cursor to be a String Also fix bug in SharedCommandTests --- java/integTest/src/test/java/glide/SharedCommandTests.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 19979f24fa..7c3afc4221 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -7621,12 +7621,6 @@ public void sscan(BaseClient client) { // Result contains the whole set assertEquals(charMembers.length, client.sadd(key1, charMembers).get()); - // Sleep after sadd() for eventual consistency. - // TODO: Replace sleep with WAIT request to enforce strong consistency. - try { - Thread.sleep(1000); - } catch (InterruptedException ex) { - } result = client.sscan(key1, initialCursor).get(); assertEquals(initialCursor, result[resultCursorIndex]); assertEquals(charMembers.length, ((Object[]) result[resultCollectionIndex]).length); From cdd69b04b509d10a575feaef0d70e01d914df1ac Mon Sep 17 00:00:00 2001 From: James Duong Date: Fri, 28 Jun 2024 18:59:47 -0700 Subject: [PATCH 29/39] Fix rebase conflicts --- .../api/commands/GenericBaseCommands.java | 3 - .../glide/api/models/BaseTransaction.java | 68 +------------------ .../test/java/glide/SharedCommandTests.java | 11 --- 3 files changed, 1 insertion(+), 81 deletions(-) diff --git a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java index 0afcb9084a..e440b969d7 100644 --- a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java @@ -1207,7 +1207,4 @@ CompletableFuture restore( * } */ CompletableFuture sortStore(String key, String destination); - CompletableFuture sscan(String key, long cursor); - - CompletableFuture sscan(String key, long cursor, ScanOptions scanOptions); } 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 6c35647718..9912a31bbe 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -5167,24 +5167,9 @@ public T geosearch( @NonNull GeoSearchOrigin.SearchOrigin searchFrom, @NonNull GeoSearchShape searchBy) { ArgsArray args = - buildArgs(concatenateArrays(new String[] {key}, searchFrom.toArgs(), searchBy.toArgs())); + buildArgs(concatenateArrays(new String[]{key}, searchFrom.toArgs(), searchBy.toArgs())); protobufTransaction.addCommands(buildCommand(GeoSearch, args)); return getThis(); - - /** - * Sorts the elements in the list, set, or sorted set at key and returns the result. - *
- * The sort command can be used to sort elements based on different criteria and - * apply transformations on sorted elements.
- * To store the result into a new key, see {@link #sortStore(String, String)}.
- * - * @param key The key of the list, set, or sorted set to be sorted. - * @return Command Response - An Array of sorted elements. - */ - public T sort(@NonNull String key) { - ArgsArray commandArgs = buildArgs(key); - protobufTransaction.addCommands(buildCommand(Sort, commandArgs)); - return getThis(); } /** @@ -5230,22 +5215,6 @@ public T geosearch( return getThis(); } - /** - * Sorts the elements in the list, set, or sorted set at key and returns the result. - *
- * The sortReadOnly command can be used to sort elements based on different criteria - * and apply transformations on sorted elements. - * - * @since Redis 7.0 and above. - * @param key The key of the list, set, or sorted set to be sorted. - * @return Command Response - An Array of sorted elements. - */ - public T sortReadOnly(@NonNull String key) { - ArgsArray commandArgs = buildArgs(key); - protobufTransaction.addCommands(buildCommand(SortReadOnly, commandArgs)); - return getThis(); - } - /** * Returns the members of a sorted set populated with geospatial information using {@link * #geoadd(String, Map)}, which are within the borders of the area specified by a given shape. @@ -5293,25 +5262,6 @@ public T geosearch( return getThis(); } - /** - * Sorts the elements in the list, set, or sorted set at key and stores the result in - * destination. The sort command can be used to sort elements based on - * different criteria, apply transformations on sorted elements, and store the result in a new - * key.
- * To get the sort result without storing it into a key, see {@link #sort(String)} or {@link - * #sortReadOnly(String)}. - * - * @param key The key of the list, set, or sorted set to be sorted. - * @param destination The key where the sorted result will be stored. - * @return Command Response - The number of elements in the sorted key stored at destination - * . - */ - public T sortStore(@NonNull String key, @NonNull String destination) { - ArgsArray commandArgs = buildArgs(new String[] {key, STORE_COMMAND_STRING, destination}); - protobufTransaction.addCommands(buildCommand(Sort, commandArgs)); - return getThis(); - } - /** * Returns the members of a sorted set populated with geospatial information using {@link * #geoadd(String, Map)}, which are within the borders of the area specified by a given shape. @@ -5366,22 +5316,6 @@ public T geosearch( return getThis(); } - /** - * Iterates incrementally over a set. - * - * @see valkey.io for details. - * @param key The key of the set. - * @param cursor The cursor that points to the next iteration of results. - * @return Command Response - An Array of Objects. The first element is - * always the cursor for the next iteration of results. 0 will be - * the cursor returned on the last iteration of the set. The second element is - * always an Array of the subset of the set held in key. - */ - public T sscan(@NonNull String key, @NonNull String cursor) { - protobufTransaction.addCommands(buildCommand(SScan, buildArgs(key, cursor))); - return getThis(); - } - /** * Searches for members in a sorted set stored at source representing geospatial data * within a circular or rectangular area and stores the result in destination. If diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 7c3afc4221..d83b9693bc 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -7863,17 +7863,6 @@ public void zscan(BaseClient client) { "secondResultAllKeys: {%s} numberMap.keySet: {%s}", secondResultAllKeys, numberMap.keySet())); - final Set numberMapValuesAsStrings = - numberMap.values().stream() - .map(d -> String.valueOf(d.intValue())) - .collect(Collectors.toSet()); - - assertTrue( - secondResultAllValues.containsAll(numberMapValuesAsStrings), - String.format( - "secondResultAllValues: {%s} numberMapValuesAsStrings: {%s}", - secondResultAllValues, numberMapValuesAsStrings)); - // Test match pattern result = client.zscan(key1, initialCursor, ZScanOptions.builder().matchPattern("*").build()).get(); From a0f1158f18d68a847899acdfa11cf4bf6dbb5f15 Mon Sep 17 00:00:00 2001 From: James Duong Date: Fri, 28 Jun 2024 19:01:25 -0700 Subject: [PATCH 30/39] Fix another rebase conflict --- java/integTest/src/test/java/glide/SharedCommandTests.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index d83b9693bc..74dbac710b 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -7857,12 +7857,6 @@ public void zscan(BaseClient client) { "secondResultAllValues: {%s} numberMapValuesAsStrings: {%s}", secondResultAllValues, numberMapValuesAsStrings)); - assertTrue( - secondResultAllKeys.containsAll(numberMap.keySet()), - String.format( - "secondResultAllKeys: {%s} numberMap.keySet: {%s}", - secondResultAllKeys, numberMap.keySet())); - // Test match pattern result = client.zscan(key1, initialCursor, ZScanOptions.builder().matchPattern("*").build()).get(); From e49be9f08e95a12f04e75986c2526f25b65ba8de Mon Sep 17 00:00:00 2001 From: James Duong Date: Fri, 28 Jun 2024 19:10:43 -0700 Subject: [PATCH 31/39] Spotless --- java/client/src/main/java/glide/api/models/BaseTransaction.java | 2 +- .../integTest/src/test/java/glide/TransactionTestUtilities.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) 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 9912a31bbe..6dacc98bc4 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -5167,7 +5167,7 @@ public T geosearch( @NonNull GeoSearchOrigin.SearchOrigin searchFrom, @NonNull GeoSearchShape searchBy) { ArgsArray args = - buildArgs(concatenateArrays(new String[]{key}, searchFrom.toArgs(), searchBy.toArgs())); + buildArgs(concatenateArrays(new String[] {key}, searchFrom.toArgs(), searchBy.toArgs())); protobufTransaction.addCommands(buildCommand(GeoSearch, args)); return getThis(); } diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index 4373280600..f0797b2db8 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -527,7 +527,6 @@ private static Object[] setCommands(BaseTransaction transaction) { transaction .sadd(setKey1, new String[] {"baz", "foo"}) - .srem(setKey1, new String[] {"foo"}) .sscan(setKey1, "0") .sscan(setKey1, "0", SScanOptions.builder().matchPattern("*").count(10L).build()) From 3a0f6963f89cbcc27f55719433d9015d3793ea13 Mon Sep 17 00:00:00 2001 From: James Duong Date: Fri, 28 Jun 2024 18:43:35 -0700 Subject: [PATCH 32/39] HScan --- glide-core/src/protobuf/redis_request.proto | 1 + glide-core/src/request_type.rs | 3 + .../src/main/java/glide/api/BaseClient.java | 15 ++ .../glide/api/commands/HashBaseCommands.java | 70 ++++++++ .../glide/api/models/BaseTransaction.java | 41 +++++ .../models/commands/scan/HScanOptions.java | 13 ++ .../test/java/glide/api/RedisClientTest.java | 54 ++++++ .../test/java/glide/SharedCommandTests.java | 161 ++++++++++++++++++ .../java/glide/TransactionTestUtilities.java | 15 +- 9 files changed, 372 insertions(+), 1 deletion(-) create mode 100644 java/client/src/main/java/glide/api/models/commands/scan/HScanOptions.java diff --git a/glide-core/src/protobuf/redis_request.proto b/glide-core/src/protobuf/redis_request.proto index 1394e95721..0c09cf9650 100644 --- a/glide-core/src/protobuf/redis_request.proto +++ b/glide-core/src/protobuf/redis_request.proto @@ -240,6 +240,7 @@ enum RequestType { XGroupSetId = 199; SScan = 200; ZScan = 201; + HScan = 202; } message Command { diff --git a/glide-core/src/request_type.rs b/glide-core/src/request_type.rs index d73c6576f8..ba6285f8ff 100644 --- a/glide-core/src/request_type.rs +++ b/glide-core/src/request_type.rs @@ -210,6 +210,7 @@ pub enum RequestType { XGroupSetId = 199, SScan = 200, ZScan = 201, + HScan = 202, } fn get_two_word_command(first: &str, second: &str) -> Cmd { @@ -423,6 +424,7 @@ impl From<::protobuf::EnumOrUnknown> for RequestType { ProtobufRequestType::XGroupSetId => RequestType::XGroupSetId, ProtobufRequestType::SScan => RequestType::SScan, ProtobufRequestType::ZScan => RequestType::ZScan, + ProtobufRequestType::HScan => RequestType::HScan, } } } @@ -634,6 +636,7 @@ impl RequestType { RequestType::XGroupSetId => Some(get_two_word_command("XGROUP", "SETID")), RequestType::SScan => Some(cmd("SSCAN")), RequestType::ZScan => Some(cmd("ZSCAN")), + RequestType::HScan => Some(cmd("HSCAN")), } } } diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 463b0564ae..ea4cb995c4 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -61,6 +61,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.HLen; import static redis_request.RedisRequestOuterClass.RequestType.HMGet; import static redis_request.RedisRequestOuterClass.RequestType.HRandField; +import static redis_request.RedisRequestOuterClass.RequestType.HScan; import static redis_request.RedisRequestOuterClass.RequestType.HSet; import static redis_request.RedisRequestOuterClass.RequestType.HSetNX; import static redis_request.RedisRequestOuterClass.RequestType.HStrlen; @@ -215,6 +216,7 @@ import glide.api.models.commands.geospatial.GeoSearchStoreOptions; import glide.api.models.commands.geospatial.GeoUnit; import glide.api.models.commands.geospatial.GeospatialData; +import glide.api.models.commands.scan.HScanOptions; import glide.api.models.commands.scan.SScanOptions; import glide.api.models.commands.scan.ZScanOptions; import glide.api.models.commands.stream.StreamAddOptions; @@ -2935,4 +2937,17 @@ public CompletableFuture zscan( String[] arguments = concatenateArrays(new String[] {key, cursor}, zScanOptions.toArgs()); return commandManager.submitNewCommand(ZScan, arguments, this::handleArrayResponse); } + + @Override + public CompletableFuture hscan(@NonNull String key, @NonNull String cursor) { + String[] arguments = new String[] {key, cursor}; + return commandManager.submitNewCommand(HScan, arguments, this::handleArrayResponse); + } + + @Override + public CompletableFuture hscan( + @NonNull String key, @NonNull String cursor, @NonNull HScanOptions hScanOptions) { + String[] arguments = concatenateArrays(new String[] {key, cursor}, hScanOptions.toArgs()); + return commandManager.submitNewCommand(HScan, arguments, this::handleArrayResponse); + } } diff --git a/java/client/src/main/java/glide/api/commands/HashBaseCommands.java b/java/client/src/main/java/glide/api/commands/HashBaseCommands.java index c5b3939aa7..e91e5f477c 100644 --- a/java/client/src/main/java/glide/api/commands/HashBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/HashBaseCommands.java @@ -2,6 +2,7 @@ package glide.api.commands; import glide.api.models.GlideString; +import glide.api.models.commands.scan.HScanOptions; import java.util.Map; import java.util.concurrent.CompletableFuture; @@ -432,4 +433,73 @@ public interface HashBaseCommands { * } */ CompletableFuture hrandfieldWithCountWithValues(String key, long count); + + /** + * Iterates fields of Hash types and their associated values. + * + * @see valkey.io for details. + * @param key The key of the hash. + * @param cursor The cursor that points to the next iteration of results. + * @return An Array of Objects. The first element is always the + * cursor for the next iteration of results. 0 will be the cursor + * returned on the last iteration of the result. The second element is always an + * Array of the subset of the hash held in key. The array in the + * second element is always a flattened series of String pairs, where the key is at even + * indices and the value is at odd indices. + * @example + *
{@code
+     * // Assume key contains a set with 200 member-score pairs
+     * String cursor = "0";
+     * Object[] result;
+     * do {
+     *   result = client.hscan(key1, cursor).get();
+     *   cursor = result[0].toString();
+     *   Object[] stringResults = (Object[]) result[1];
+     *
+     *   System.out.println("\nHSCAN iteration:");
+     *   for (int i = 0; i < stringResults.length; i += 2) {
+     *     System.out.printf("{%s=%s}", stringResults[i], stringResults[i + 1]);
+     *     if (i + 2 < stringResults.length) {
+     *       System.out.print(", ");
+     *     }
+     *   }
+     * } while (!cursor.equals("0"));
+     * }
+ */ + CompletableFuture hscan(String key, String cursor); + + /** + * Iterates fields of Hash types and their associated values. + * + * @see valkey.io for details. + * @param key The key of the hash. + * @param cursor The cursor that points to the next iteration of results. + * @param hScanOptions The {@link HScanOptions}. + * @return An Array of Objects. The first element is always the + * cursor for the next iteration of results. 0 will be the cursor + * returned on the last iteration of the result. The second element is always an + * Array of the subset of the hash held in key. The array in the + * second element is always a flattened series of String pairs, where the key is at even + * indices and the value is at odd indices. + * @example + *
{@code
+     * // Assume key contains a set with 200 member-score pairs
+     * String cursor = "0";
+     * Object[] result;
+     * do {
+     *   result = client.hscan(key1, cursor, HScanOptions.builder().matchPattern("*").count(20L).build()).get();
+     *   cursor = result[0].toString();
+     *   Object[] stringResults = (Object[]) result[1];
+     *
+     *   System.out.println("\nHSCAN iteration:");
+     *   for (int i = 0; i < stringResults.length; i += 2) {
+     *     System.out.printf("{%s=%s}", stringResults[i], stringResults[i + 1]);
+     *     if (i + 2 < stringResults.length) {
+     *       System.out.print(", ");
+     *     }
+     *   }
+     * } while (!cursor.equals("0"));
+     * }
+ */ + CompletableFuture hscan(String key, String cursor, HScanOptions hScanOptions); } 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 6dacc98bc4..63a2147122 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -84,6 +84,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.HLen; import static redis_request.RedisRequestOuterClass.RequestType.HMGet; import static redis_request.RedisRequestOuterClass.RequestType.HRandField; +import static redis_request.RedisRequestOuterClass.RequestType.HScan; import static redis_request.RedisRequestOuterClass.RequestType.HSet; import static redis_request.RedisRequestOuterClass.RequestType.HSetNX; import static redis_request.RedisRequestOuterClass.RequestType.HStrlen; @@ -249,6 +250,7 @@ import glide.api.models.commands.geospatial.GeoSearchStoreOptions; import glide.api.models.commands.geospatial.GeoUnit; import glide.api.models.commands.geospatial.GeospatialData; +import glide.api.models.commands.scan.HScanOptions; import glide.api.models.commands.scan.SScanOptions; import glide.api.models.commands.scan.ZScanOptions; import glide.api.models.commands.stream.StreamAddOptions; @@ -5582,6 +5584,45 @@ public T zscan(@NonNull String key, @NonNull String cursor, @NonNull ZScanOption return getThis(); } + /** + * Iterates fields of Hash types and their associated values. + * + * @see valkey.io for details. + * @param key The key of the hash. + * @param cursor The cursor that points to the next iteration of results. + * @return Command Response - An Array of Objects. The first element is + * always the cursor for the next iteration of results. 0 will be + * the cursor returned on the last iteration of the result. The second element is + * always an Array of the subset of the hash held in key. The array + * in the second element is always a flattened series of String pairs, where the key is at + * even indices and the value is at odd indices. + */ + public T hscan(@NonNull String key, @NonNull String cursor) { + protobufTransaction.addCommands(buildCommand(HScan, buildArgs(key, cursor))); + return getThis(); + } + + /** + * Iterates fields of Hash types and their associated values. + * + * @see valkey.io for details. + * @param key The key of the hash. + * @param cursor The cursor that points to the next iteration of results. + * @param hScanOptions The {@link HScanOptions}. + * @return Command Response - An Array of Objects. The first element is + * always the cursor for the next iteration of results. 0 will be + * the cursor returned on the last iteration of the result. The second element is + * always an Array of the subset of the hash held in key. The array + * in the second element is always a flattened series of String pairs, where the key is at + * even indices and the value is at odd indices. + */ + public T hscan(@NonNull String key, @NonNull String cursor, @NonNull HScanOptions hScanOptions) { + final ArgsArray commandArgs = + buildArgs(concatenateArrays(new String[] {key, cursor}, hScanOptions.toArgs())); + protobufTransaction.addCommands(buildCommand(HScan, 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/main/java/glide/api/models/commands/scan/HScanOptions.java b/java/client/src/main/java/glide/api/models/commands/scan/HScanOptions.java new file mode 100644 index 0000000000..a6ec51e56e --- /dev/null +++ b/java/client/src/main/java/glide/api/models/commands/scan/HScanOptions.java @@ -0,0 +1,13 @@ +/** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */ +package glide.api.models.commands.scan; + +import glide.api.commands.HashBaseCommands; +import lombok.experimental.SuperBuilder; + +/** + * Optional arguments for {@link HashBaseCommands#hscan(String, String, HScanOptions)}. + * + * @see valkey.io + */ +@SuperBuilder +public class HScanOptions extends ScanOptions {} diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 1eabe0776f..03a6a0f181 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -138,6 +138,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.HLen; import static redis_request.RedisRequestOuterClass.RequestType.HMGet; import static redis_request.RedisRequestOuterClass.RequestType.HRandField; +import static redis_request.RedisRequestOuterClass.RequestType.HScan; import static redis_request.RedisRequestOuterClass.RequestType.HSet; import static redis_request.RedisRequestOuterClass.RequestType.HSetNX; import static redis_request.RedisRequestOuterClass.RequestType.HStrlen; @@ -309,6 +310,7 @@ import glide.api.models.commands.geospatial.GeoSearchStoreOptions; import glide.api.models.commands.geospatial.GeoUnit; import glide.api.models.commands.geospatial.GeospatialData; +import glide.api.models.commands.scan.HScanOptions; import glide.api.models.commands.scan.SScanOptions; import glide.api.models.commands.scan.ZScanOptions; import glide.api.models.commands.stream.StreamAddOptions; @@ -9079,6 +9081,58 @@ public void zscan_with_options_returns_success() { assertEquals(value, payload); } + @SneakyThrows + @Test + public void hscan_returns_success() { + // setup + String key = "testKey"; + String cursor = "0"; + String[] arguments = new String[] {key, cursor}; + Object[] value = new Object[] {0L, new String[] {"hello", "world"}}; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(HScan), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.hscan(key, cursor); + Object[] payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void hscan_with_options_returns_success() { + // setup + String key = "testKey"; + String cursor = "0"; + String[] arguments = + new String[] {key, cursor, MATCH_OPTION_STRING, "*", COUNT_OPTION_STRING, "1"}; + Object[] value = new Object[] {0L, new String[] {"hello", "world"}}; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(HScan), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = + service.hscan(key, cursor, HScanOptions.builder().matchPattern("*").count(1L).build()); + Object[] payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + private static List getGeoSearchArguments() { return List.of( Arguments.of( diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 74dbac710b..a6417caec0 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -78,6 +78,7 @@ import glide.api.models.commands.geospatial.GeoSearchStoreOptions; import glide.api.models.commands.geospatial.GeoUnit; import glide.api.models.commands.geospatial.GeospatialData; +import glide.api.models.commands.scan.HScanOptions; import glide.api.models.commands.scan.SScanOptions; import glide.api.models.commands.scan.ZScanOptions; import glide.api.models.commands.stream.StreamAddOptions; @@ -7903,4 +7904,164 @@ public void zscan(BaseClient client) { () -> client.zscan(key1, "-1", ZScanOptions.builder().count(-1L).build()).get()); assertInstanceOf(RequestException.class, executionException.getCause()); } + + @SneakyThrows + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("getClients") + public void hscan(BaseClient client) { + String key1 = "{key}-1" + UUID.randomUUID(); + String key2 = "{key}-2" + UUID.randomUUID(); + String initialCursor = "0"; + long defaultCount = 20; + int resultCursorIndex = 0; + int resultCollectionIndex = 1; + + // Setup test data + Map numberMap = new HashMap<>(); + // This is an unusually large dataset because the server can ignore the COUNT option + // if the dataset is small enough that it is more efficient to transfer its entire contents + // at once. + for (int i = 0; i < 100000; i++) { + numberMap.put(String.valueOf(i), "num" + i); + } + String[] charMembers = new String[] {"a", "b", "c", "d", "e"}; + Map charMap = new HashMap<>(); + for (int i = 0; i < 5; i++) { + charMap.put(charMembers[i], String.valueOf(i)); + } + + // Empty set + Object[] result = client.hscan(key1, initialCursor).get(); + assertEquals(initialCursor, result[resultCursorIndex]); + assertDeepEquals(new String[] {}, result[resultCollectionIndex]); + + // Negative cursor + result = client.hscan(key1, "-1").get(); + assertEquals(initialCursor, result[resultCursorIndex]); + assertDeepEquals(new String[] {}, result[resultCollectionIndex]); + + // Result contains the whole set + assertEquals(charMembers.length, client.hset(key1, charMap).get()); + result = client.hscan(key1, initialCursor).get(); + assertEquals(initialCursor, result[resultCursorIndex]); + assertEquals( + charMap.size() * 2, + ((Object[]) result[resultCollectionIndex]) + .length); // Length includes the score which is twice the map size + final Object[] resultArray = (Object[]) result[resultCollectionIndex]; + + final Set resultKeys = new HashSet<>(); + final Set resultValues = new HashSet<>(); + for (int i = 0; i < resultArray.length; i += 2) { + resultKeys.add(resultArray[i]); + resultValues.add(resultArray[i + 1]); + } + assertTrue( + resultKeys.containsAll(charMap.keySet()), + String.format("resultKeys: {%s} charMap.keySet(): {%s}", resultKeys, charMap.keySet())); + + assertTrue( + resultValues.containsAll(charMap.values()), + String.format("resultValues: {%s} charMap.values(): {%s}", resultValues, charMap.values())); + + result = + client.hscan(key1, initialCursor, HScanOptions.builder().matchPattern("a").build()).get(); + assertEquals(initialCursor, result[resultCursorIndex]); + assertDeepEquals(new String[] {"a", "0"}, result[resultCollectionIndex]); + + // Result contains a subset of the key + final HashMap combinedMap = new HashMap<>(numberMap); + combinedMap.putAll(charMap); + assertEquals(numberMap.size(), client.hset(key1, combinedMap).get()); + String resultCursor = "0"; + final Set secondResultAllKeys = new HashSet<>(); + final Set secondResultAllValues = new HashSet<>(); + do { + result = client.hscan(key1, resultCursor).get(); + resultCursor = result[resultCursorIndex].toString(); + Object[] resultEntry = (Object[]) result[resultCollectionIndex]; + for (int i = 0; i < resultEntry.length; i += 2) { + secondResultAllKeys.add(resultEntry[i]); + secondResultAllValues.add(resultEntry[i + 1]); + } + + if (resultCursor.equals("0")) { + break; + } + + // Scan with result cursor has a different set + Object[] secondResult = client.hscan(key1, resultCursor).get(); + String newResultCursor = secondResult[resultCursorIndex].toString(); + assertNotEquals(resultCursor, newResultCursor); + resultCursor = newResultCursor; + Object[] secondResultEntry = (Object[]) secondResult[resultCollectionIndex]; + assertFalse( + Arrays.deepEquals( + ArrayUtils.toArray(result[resultCollectionIndex]), + ArrayUtils.toArray(secondResult[resultCollectionIndex]))); + + for (int i = 0; i < secondResultEntry.length; i += 2) { + secondResultAllKeys.add(secondResultEntry[i]); + secondResultAllValues.add(secondResultEntry[i + 1]); + } + } while (!resultCursor.equals("0")); // 0 is returned for the cursor of the last iteration. + + assertTrue( + secondResultAllKeys.containsAll(numberMap.keySet()), + String.format( + "secondResultAllKeys: {%s} numberMap.keySet: {%s}", + secondResultAllKeys, numberMap.keySet())); + + assertTrue( + secondResultAllValues.containsAll(numberMap.values()), + String.format( + "secondResultAllValues: {%s} numberMap.values(): {%s}", + secondResultAllValues, numberMap.values())); + + // Test match pattern + result = + client.hscan(key1, initialCursor, HScanOptions.builder().matchPattern("*").build()).get(); + assertTrue(Long.parseLong(result[resultCursorIndex].toString()) >= 0); + assertTrue(ArrayUtils.getLength(result[resultCollectionIndex]) >= defaultCount); + + // Test count + result = client.hscan(key1, initialCursor, HScanOptions.builder().count(20L).build()).get(); + assertTrue(Long.parseLong(result[resultCursorIndex].toString()) >= 0); + assertTrue(ArrayUtils.getLength(result[resultCollectionIndex]) >= 20); + + // Test count with match returns a non-empty list + result = + client + .hscan( + key1, initialCursor, HScanOptions.builder().matchPattern("1*").count(20L).build()) + .get(); + assertTrue(Long.parseLong(result[resultCursorIndex].toString()) >= 0); + assertTrue(ArrayUtils.getLength(result[resultCollectionIndex]) > 0); + + // Exceptions + // Non-hash key + assertEquals(OK, client.set(key2, "test").get()); + ExecutionException executionException = + assertThrows(ExecutionException.class, () -> client.hscan(key2, initialCursor).get()); + assertInstanceOf(RequestException.class, executionException.getCause()); + + executionException = + assertThrows( + ExecutionException.class, + () -> + client + .hscan( + key2, + initialCursor, + HScanOptions.builder().matchPattern("test").count(1L).build()) + .get()); + assertInstanceOf(RequestException.class, executionException.getCause()); + + // Negative count + executionException = + assertThrows( + ExecutionException.class, + () -> client.hscan(key1, "-1", HScanOptions.builder().count(-1L).build()).get()); + assertInstanceOf(RequestException.class, executionException.getCause()); + } } diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index f0797b2db8..87edb4e6c2 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -42,6 +42,7 @@ import glide.api.models.commands.geospatial.GeoSearchStoreOptions; import glide.api.models.commands.geospatial.GeoUnit; import glide.api.models.commands.geospatial.GeospatialData; +import glide.api.models.commands.scan.HScanOptions; import glide.api.models.commands.scan.SScanOptions; import glide.api.models.commands.scan.ZScanOptions; import glide.api.models.commands.stream.StreamAddOptions; @@ -351,6 +352,10 @@ private static Object[] stringCommands(BaseTransaction transaction) { private static Object[] hashCommands(BaseTransaction transaction) { String hashKey1 = "{HashKey}-1-" + UUID.randomUUID(); + // This extra key is for HScan testing. It is a key with only one field. HScan doesn't guarantee + // a return order but this test compares arrays so order is significant. + String hashKey2 = "{HashKey}-2-" + UUID.randomUUID(); + transaction .hset(hashKey1, Map.of(field1, value1, field2, value2)) .hget(hashKey1, field1) @@ -369,7 +374,10 @@ private static Object[] hashCommands(BaseTransaction transaction) { .hincrBy(hashKey1, field3, 5) .hincrByFloat(hashKey1, field3, 5.5) .hkeys(hashKey1) - .hstrlen(hashKey1, field2); + .hstrlen(hashKey1, field2) + .hset(hashKey2, Map.of(field1, value1)) + .hscan(hashKey2, "0") + .hscan(hashKey2, "0", HScanOptions.builder().count(20L).build()); return new Object[] { 2L, // hset(hashKey1, Map.of(field1, value1, field2, value2)) @@ -392,6 +400,11 @@ private static Object[] hashCommands(BaseTransaction transaction) { 10.5, // hincrByFloat(hashKey1, field3, 5.5) new String[] {field2, field3}, // hkeys(hashKey1) (long) value2.length(), // hstrlen(hashKey1, field2) + 1L, // hset(hashKey2, Map.of(field1, value1)) + new Object[] {"0", new Object[] {field1, value1}}, // hscan(hashKey2, "0") + new Object[] { + "0", new Object[] {field1, value1} + }, // hscan(hashKey2, "0", HScanOptions.builder().count(20L).build()); }; } From f41053178b26086ecb0583e2f14e42d645359386 Mon Sep 17 00:00:00 2001 From: James Duong Date: Fri, 28 Jun 2024 20:56:42 -0700 Subject: [PATCH 33/39] Flakey test --- java/integTest/src/test/java/glide/SharedCommandTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index a6417caec0..e863a30ef6 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -8036,7 +8036,7 @@ public void hscan(BaseClient client) { key1, initialCursor, HScanOptions.builder().matchPattern("1*").count(20L).build()) .get(); assertTrue(Long.parseLong(result[resultCursorIndex].toString()) >= 0); - assertTrue(ArrayUtils.getLength(result[resultCollectionIndex]) > 0); + assertTrue(ArrayUtils.getLength(result[resultCollectionIndex]) >= 0); // Exceptions // Non-hash key From c9ec010e90d7b400c85f99cae6cdd4f98ff11697 Mon Sep 17 00:00:00 2001 From: James Duong Date: Sat, 29 Jun 2024 06:16:49 -0700 Subject: [PATCH 34/39] Add HScan transaction unit test --- .../src/test/java/glide/api/models/TransactionTests.java | 8 ++++++++ 1 file changed, 8 insertions(+) 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 9194c61cb3..0d0297c6e4 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -105,6 +105,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.HLen; import static redis_request.RedisRequestOuterClass.RequestType.HMGet; import static redis_request.RedisRequestOuterClass.RequestType.HRandField; +import static redis_request.RedisRequestOuterClass.RequestType.HScan; import static redis_request.RedisRequestOuterClass.RequestType.HSet; import static redis_request.RedisRequestOuterClass.RequestType.HSetNX; import static redis_request.RedisRequestOuterClass.RequestType.HStrlen; @@ -260,6 +261,7 @@ import glide.api.models.commands.geospatial.GeoSearchStoreOptions; import glide.api.models.commands.geospatial.GeoUnit; import glide.api.models.commands.geospatial.GeospatialData; +import glide.api.models.commands.scan.HScanOptions; import glide.api.models.commands.scan.SScanOptions; import glide.api.models.commands.scan.ZScanOptions; import glide.api.models.commands.stream.StreamAddOptions; @@ -1376,6 +1378,12 @@ InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3, false), new Limit(1, 2)), transaction.zscan("key1", "0", ZScanOptions.builder().matchPattern("*").count(10L).build()); results.add(Pair.of(ZScan, buildArgs("key1", "0", "MATCH", "*", "COUNT", "10"))); + transaction.hscan("key1", "0"); + results.add(Pair.of(HScan, buildArgs("key1", "0"))); + + transaction.hscan("key1", "0", HScanOptions.builder().matchPattern("*").count(10L).build()); + results.add(Pair.of(HScan, buildArgs("key1", "0", "MATCH", "*", "COUNT", "10"))); + var protobufTransaction = transaction.getProtobufTransaction().build(); for (int idx = 0; idx < protobufTransaction.getCommandsCount(); idx++) { From 241ef45b890f3f9cceb7ce460f4d36c913d0e867 Mon Sep 17 00:00:00 2001 From: James Duong Date: Sat, 29 Jun 2024 06:20:19 -0700 Subject: [PATCH 35/39] Rename ScanOptions to BaseScanOptions --- .../models/commands/scan/HScanOptions.java | 2 +- .../api/models/commands/scan/ScanOptions.java | 59 ------------------- 2 files changed, 1 insertion(+), 60 deletions(-) delete mode 100644 java/client/src/main/java/glide/api/models/commands/scan/ScanOptions.java diff --git a/java/client/src/main/java/glide/api/models/commands/scan/HScanOptions.java b/java/client/src/main/java/glide/api/models/commands/scan/HScanOptions.java index a6ec51e56e..1f03a00e6d 100644 --- a/java/client/src/main/java/glide/api/models/commands/scan/HScanOptions.java +++ b/java/client/src/main/java/glide/api/models/commands/scan/HScanOptions.java @@ -10,4 +10,4 @@ * @see valkey.io */ @SuperBuilder -public class HScanOptions extends ScanOptions {} +public class HScanOptions extends BaseScanOptions {} diff --git a/java/client/src/main/java/glide/api/models/commands/scan/ScanOptions.java b/java/client/src/main/java/glide/api/models/commands/scan/ScanOptions.java deleted file mode 100644 index f02345f5e3..0000000000 --- a/java/client/src/main/java/glide/api/models/commands/scan/ScanOptions.java +++ /dev/null @@ -1,59 +0,0 @@ -/** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */ -package glide.api.models.commands.scan; - -import java.util.ArrayList; -import java.util.List; -import lombok.experimental.SuperBuilder; - -/** - * This base class represents the common set of optional arguments for the SCAN family of commands. - * Concrete implementations of this class are tied to specific SCAN commands (SCAN, HSCAN, SSCAN, - * and ZSCAN). - */ -@SuperBuilder -public abstract class ScanOptions { - /** MATCH option string to include in the SCAN commands. */ - public static final String MATCH_OPTION_STRING = "MATCH"; - - /** COUNT option string to include in the SCAN commands. */ - public static final String COUNT_OPTION_STRING = "COUNT"; - - /** - * The match filter is applied to the result of the command and will only include strings that - * match the pattern specified. If the set, hash, or list is large enough for scan commands to - * return only a subset of the set, hash, or list, then there could be a case where the result is - * empty although there are items that match the pattern specified. This is due to the default - * COUNT being 10 which indicates that it will only fetch and match - * 10 items from the list. - */ - private final String matchPattern; - - /** - * COUNT is a just a hint for the command for how many elements to fetch from the - * set, hash, or list. COUNT could be ignored until the set, hash, or list is large - * enough for the SCAN commands to represent the results as compact single-allocation - * packed encoding. - */ - private final Long count; - - /** - * Creates the arguments to be used in SCAN commands. - * - * @return a String array that holds the options and their arguments. - */ - public String[] toArgs() { - List optionArgs = new ArrayList<>(); - - if (matchPattern != null) { - optionArgs.add(MATCH_OPTION_STRING); - optionArgs.add(matchPattern); - } - - if (count != null) { - optionArgs.add(COUNT_OPTION_STRING); - optionArgs.add(count.toString()); - } - - return optionArgs.toArray(new String[0]); - } -} From fc2a4d1ea422f2c6f5bfcca89daacdfe3bc16a56 Mon Sep 17 00:00:00 2001 From: James Duong Date: Sat, 29 Jun 2024 12:09:18 -0700 Subject: [PATCH 36/39] Fix merge issues --- .../test/java/glide/SharedCommandTests.java | 24 +++++-------------- 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index e863a30ef6..0c419fd5e5 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -7610,16 +7610,6 @@ public void sscan(BaseClient client) { assertEquals(initialCursor, result[resultCursorIndex]); assertDeepEquals(new String[] {}, result[resultCollectionIndex]); - // Negative cursor - result = client.sscan(key1, "-1").get(); - assertEquals(initialCursor, result[resultCursorIndex]); - assertDeepEquals(new String[] {}, result[resultCollectionIndex]); - - // Negative cursor - result = client.sscan(key1, "-1").get(); - assertEquals(initialCursor, result[resultCursorIndex]); - assertDeepEquals(new String[] {}, result[resultCollectionIndex]); - // Result contains the whole set assertEquals(charMembers.length, client.sadd(key1, charMembers).get()); result = client.sscan(key1, initialCursor).get(); @@ -7680,12 +7670,6 @@ public void sscan(BaseClient client) { "secondResultValues: {%s}, numberMembersSet: {%s}", secondResultValues, numberMembersSet)); - assertTrue( - secondResultValues.containsAll(numberMembersSet), - String.format( - "secondResultValues: {%s}, numberMembersSet: {%s}", - secondResultValues, numberMembersSet)); - // Test match pattern result = client.sscan(key1, initialCursor, SScanOptions.builder().matchPattern("*").build()).get(); @@ -7921,7 +7905,7 @@ public void hscan(BaseClient client) { // This is an unusually large dataset because the server can ignore the COUNT option // if the dataset is small enough that it is more efficient to transfer its entire contents // at once. - for (int i = 0; i < 100000; i++) { + for (int i = 0; i < 50000; i++) { numberMap.put(String.valueOf(i), "num" + i); } String[] charMembers = new String[] {"a", "b", "c", "d", "e"}; @@ -7976,6 +7960,7 @@ public void hscan(BaseClient client) { String resultCursor = "0"; final Set secondResultAllKeys = new HashSet<>(); final Set secondResultAllValues = new HashSet<>(); + boolean isFirstLoop = true; do { result = client.hscan(key1, resultCursor).get(); resultCursor = result[resultCursorIndex].toString(); @@ -7985,7 +7970,10 @@ public void hscan(BaseClient client) { secondResultAllValues.add(resultEntry[i + 1]); } - if (resultCursor.equals("0")) { + if (isFirstLoop) { + assertNotEquals("0", resultCursor); + isFirstLoop = false; + } else if (resultCursor.equals("0")) { break; } From 7eef0c160ba8c39bf7bb908657fa2f11354f1563 Mon Sep 17 00:00:00 2001 From: James Duong Date: Sat, 29 Jun 2024 12:09:27 -0700 Subject: [PATCH 37/39] Fix module-info ordering --- java/client/src/main/java/module-info.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/client/src/main/java/module-info.java b/java/client/src/main/java/module-info.java index 1cff595006..2dbaca04f7 100644 --- a/java/client/src/main/java/module-info.java +++ b/java/client/src/main/java/module-info.java @@ -6,10 +6,10 @@ exports glide.api.models.commands.bitmap; exports glide.api.models.commands.geospatial; exports glide.api.models.commands.function; + exports glide.api.models.commands.scan; exports glide.api.models.commands.stream; exports glide.api.models.configuration; exports glide.api.models.exceptions; - exports glide.api.models.commands.scan; requires com.google.protobuf; requires io.netty.codec; From f1429acf53e87de8d177c8ddf364cde606a90179 Mon Sep 17 00:00:00 2001 From: James Duong Date: Sat, 29 Jun 2024 12:14:21 -0700 Subject: [PATCH 38/39] Tidy up docs --- .../glide/api/commands/HashBaseCommands.java | 18 ++++---- .../glide/api/commands/SetBaseCommands.java | 12 +++--- .../api/commands/SortedSetBaseCommands.java | 12 +++--- .../glide/api/models/BaseTransaction.java | 42 ++++++++++--------- 4 files changed, 44 insertions(+), 40 deletions(-) diff --git a/java/client/src/main/java/glide/api/commands/HashBaseCommands.java b/java/client/src/main/java/glide/api/commands/HashBaseCommands.java index e91e5f477c..5691629ca0 100644 --- a/java/client/src/main/java/glide/api/commands/HashBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/HashBaseCommands.java @@ -439,13 +439,14 @@ public interface HashBaseCommands { * * @see valkey.io for details. * @param key The key of the hash. - * @param cursor The cursor that points to the next iteration of results. + * @param cursor The cursor that points to the next iteration of results. A value of "0" + * indicates the start of the search. * @return An Array of Objects. The first element is always the - * cursor for the next iteration of results. 0 will be the cursor + * cursor for the next iteration of results. "0" will be the cursor * returned on the last iteration of the result. The second element is always an * Array of the subset of the hash held in key. The array in the - * second element is always a flattened series of String pairs, where the key is at even - * indices and the value is at odd indices. + * second element is always a flattened series of String pairs, where the key is + * at even indices and the value is at odd indices. * @example *
{@code
      * // Assume key contains a set with 200 member-score pairs
@@ -473,14 +474,15 @@ public interface HashBaseCommands {
      *
      * @see valkey.io for details.
      * @param key The key of the hash.
-     * @param cursor The cursor that points to the next iteration of results.
+     * @param cursor The cursor that points to the next iteration of results. A value of "0"
+     *      indicates the start of the search.
      * @param hScanOptions The {@link HScanOptions}.
      * @return An Array of Objects. The first element is always the 
-     *     cursor for the next iteration of results. 0 will be the cursor
+     *     cursor for the next iteration of results. "0" will be the cursor
      *      returned on the last iteration of the result. The second element is always an
      *     Array of the subset of the hash held in key. The array in the
-     *     second element is always a flattened series of String pairs, where the key is at even
-     *     indices and the value is at odd indices.
+     *     second element is always a flattened series of String pairs, where the key is
+     *     at even indices and the value is at odd indices.
      * @example
      *     
{@code
      * // Assume key contains a set with 200 member-score pairs
diff --git a/java/client/src/main/java/glide/api/commands/SetBaseCommands.java b/java/client/src/main/java/glide/api/commands/SetBaseCommands.java
index bb9ec55f79..a5fa16d05c 100644
--- a/java/client/src/main/java/glide/api/commands/SetBaseCommands.java
+++ b/java/client/src/main/java/glide/api/commands/SetBaseCommands.java
@@ -560,10 +560,10 @@ public interface SetBaseCommands {
      *
      * @see valkey.io for details.
      * @param key The key of the set.
-     * @param cursor The cursor that points to the next iteration of results. A value of 0 indicates
-     *     the start of the search.
+     * @param cursor The cursor that points to the next iteration of results. A value of "0"
+     *      indicates the start of the search.
      * @return An Array of Objects. The first element is always the 
-     *     cursor for the next iteration of results. 0 will be the cursor
+     *     cursor for the next iteration of results. "0" will be the cursor
      *      returned on the last iteration of the set. The second element is always an 
      *     Array of the subset of the set held in key.
      * @example
@@ -588,11 +588,11 @@ public interface SetBaseCommands {
      *
      * @see valkey.io for details.
      * @param key The key of the set.
-     * @param cursor The cursor that points to the next iteration of results. A value of 0 indicates
-     *     the start of the search.
+     * @param cursor The cursor that points to the next iteration of results. A value of "0"
+     *      indicates the start of the search.
      * @param sScanOptions The {@link SScanOptions}.
      * @return An Array of Objects. The first element is always the 
-     *     cursor for the next iteration of results. 0 will be the cursor
+     *     cursor for the next iteration of results. "0" will be the cursor
      *      returned on the last iteration of the set. The second element is always an 
      *     Array of the subset of the set held in key.
      * @example
diff --git a/java/client/src/main/java/glide/api/commands/SortedSetBaseCommands.java b/java/client/src/main/java/glide/api/commands/SortedSetBaseCommands.java
index 7a35c84250..f6051cdb32 100644
--- a/java/client/src/main/java/glide/api/commands/SortedSetBaseCommands.java
+++ b/java/client/src/main/java/glide/api/commands/SortedSetBaseCommands.java
@@ -1584,10 +1584,10 @@ CompletableFuture> zinterWithScores(
      *
      * @see valkey.io for details.
      * @param key The key of the sorted set.
-     * @param cursor The cursor that points to the next iteration of results. A value of 0 indicates
-     *     the start of the search.
+     * @param cursor The cursor that points to the next iteration of results. A value of "0"
+     *      indicates the start of the search.
      * @return An Array of Objects. The first element is always the 
-     *     cursor for the next iteration of results. 0 will be the cursor
+     *     cursor for the next iteration of results. "0" will be the cursor
      *      returned on the last iteration of the sorted set. The second element is always an
      *     
      *     Array of the subset of the sorted set held in key. The array in the
@@ -1620,11 +1620,11 @@ CompletableFuture> zinterWithScores(
      *
      * @see valkey.io for details.
      * @param key The key of the sorted set.
-     * @param cursor The cursor that points to the next iteration of results. A value of 0 indicates
-     *     the start of the search.
+     * @param cursor The cursor that points to the next iteration of results. A value of "0"
+     *      indicates the start of the search.
      * @param zScanOptions The {@link ZScanOptions}.
      * @return An Array of Objects. The first element is always the 
-     *     cursor for the next iteration of results. 0 will be the cursor
+     *     cursor for the next iteration of results. "0" will be the cursor
      *      returned on the last iteration of the sorted set. The second element is always an
      *     
      *     Array of the subset of the sorted set held in key. The array in the
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 63a2147122..6bfef677fa 100644
--- a/java/client/src/main/java/glide/api/models/BaseTransaction.java
+++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java
@@ -5511,10 +5511,10 @@ public T geosearchstore(
      *
      * @see valkey.io for details.
      * @param key The key of the set.
-     * @param cursor The cursor that points to the next iteration of results. A value of 0 indicates
-     *     the start of the search.
+     * @param cursor The cursor that points to the next iteration of results. A value of "0"
+     *      indicates the start of the search.
      * @return Command Response - An Array of Objects. The first element is
-     *     always the cursor for the next iteration of results. 0 will be
+     *     always the cursor for the next iteration of results. "0" will be
      *     the cursor returned on the last iteration of the set. The second element is
      *     always an Array of the subset of the set held in key.
      */
@@ -5528,11 +5528,11 @@ public T sscan(@NonNull String key, @NonNull String cursor) {
      *
      * @see valkey.io for details.
      * @param key The key of the set.
-     * @param cursor The cursor that points to the next iteration of results. A value of 0 indicates
-     *     the start of the search.
+     * @param cursor The cursor that points to the next iteration of results. A value of "0"
+     *      indicates the start of the search.
      * @param sScanOptions The {@link SScanOptions}.
      * @return Command Response - An Array of Objects. The first element is
-     *     always the cursor for the next iteration of results. 0 will be
+     *     always the cursor for the next iteration of results. "0" will be
      *     the cursor returned on the last iteration of the set. The second element is
      *     always an Array of the subset of the set held in key.
      */
@@ -5548,10 +5548,10 @@ public T sscan(@NonNull String key, @NonNull String cursor, @NonNull SScanOption
      *
      * @see valkey.io for details.
      * @param key The key of the sorted set.
-     * @param cursor The cursor that points to the next iteration of results. A value of 0 indicates
-     *     the start of the search.
+     * @param cursor The cursor that points to the next iteration of results. A value of "0"
+     *      indicates the start of the search.
      * @return Command Response - An Array of Objects. The first element is
-     *     always the cursor for the next iteration of results. 0 will be
+     *     always the cursor for the next iteration of results. "0" will be
      *     the cursor returned on the last iteration of the sorted set. The second
      *     element is always an Array of the subset of the sorted set held in key
      *     . The array in the second element is always a flattened series of String
@@ -5567,11 +5567,11 @@ public T zscan(@NonNull String key, @NonNull String cursor) {
      *
      * @see valkey.io for details.
      * @param key The key of the sorted set.
-     * @param cursor The cursor that points to the next iteration of results. A value of 0 indicates
-     *     the start of the search.
+     * @param cursor The cursor that points to the next iteration of results. A value of "0"
+     *      indicates the start of the search.
      * @param zScanOptions The {@link ZScanOptions}.
      * @return Command Response - An Array of Objects. The first element is
-     *     always the cursor for the next iteration of results. 0 will be
+     *     always the cursor for the next iteration of results. "0" will be
      *     the cursor returned on the last iteration of the sorted set. The second
      *     element is always an Array of the subset of the sorted set held in key
      *     . The array in the second element is always a flattened series of String
@@ -5589,13 +5589,14 @@ public T zscan(@NonNull String key, @NonNull String cursor, @NonNull ZScanOption
      *
      * @see valkey.io for details.
      * @param key The key of the hash.
-     * @param cursor The cursor that points to the next iteration of results.
+     * @param cursor The cursor that points to the next iteration of results. A value of "0"
+     *      indicates the start of the search.
      * @return Command Response - An Array of Objects. The first element is
-     *     always the cursor for the next iteration of results. 0 will be
+     *     always the cursor for the next iteration of results. "0" will be
      *     the cursor returned on the last iteration of the result. The second element is
      *     always an Array of the subset of the hash held in key. The array
-     *     in the second element is always a flattened series of String pairs, where the key is at
-     *     even indices and the value is at odd indices.
+     *     in the second element is always a flattened series of String pairs, where the
+     *     key is at even indices and the value is at odd indices.
      */
     public T hscan(@NonNull String key, @NonNull String cursor) {
         protobufTransaction.addCommands(buildCommand(HScan, buildArgs(key, cursor)));
@@ -5607,14 +5608,15 @@ public T hscan(@NonNull String key, @NonNull String cursor) {
      *
      * @see valkey.io for details.
      * @param key The key of the hash.
-     * @param cursor The cursor that points to the next iteration of results.
+     * @param cursor The cursor that points to the next iteration of results. A value of "0"
+     *      indicates the start of the search.
      * @param hScanOptions The {@link HScanOptions}.
      * @return Command Response - An Array of Objects. The first element is
-     *     always the cursor for the next iteration of results. 0 will be
+     *     always the cursor for the next iteration of results. "0" will be
      *     the cursor returned on the last iteration of the result. The second element is
      *     always an Array of the subset of the hash held in key. The array
-     *     in the second element is always a flattened series of String pairs, where the key is at
-     *     even indices and the value is at odd indices.
+     *     in the second element is always a flattened series of String pairs, where the
+     *     key is at even indices and the value is at odd indices.
      */
     public T hscan(@NonNull String key, @NonNull String cursor, @NonNull HScanOptions hScanOptions) {
         final ArgsArray commandArgs =

From b959c6cea8d9f16fa96ff8bf31ecbcfcd355fbd9 Mon Sep 17 00:00:00 2001
From: James Duong 
Date: Sat, 29 Jun 2024 13:38:30 -0700
Subject: [PATCH 39/39] PR comments

Fix up merge duplication and use HScanOptions constants.
---
 .../java/glide/api/models/TransactionTests.java | 17 ++++++++++-------
 1 file changed, 10 insertions(+), 7 deletions(-)

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 0d0297c6e4..7ff892095b 100644
--- a/java/client/src/test/java/glide/api/models/TransactionTests.java
+++ b/java/client/src/test/java/glide/api/models/TransactionTests.java
@@ -1372,17 +1372,20 @@ InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3, false), new Limit(1, 2)),
                                 ZScanOptions.COUNT_OPTION_STRING,
                                 "10")));
 
-        transaction.zscan("key1", "0");
-        results.add(Pair.of(ZScan, buildArgs("key1", "0")));
-
-        transaction.zscan("key1", "0", ZScanOptions.builder().matchPattern("*").count(10L).build());
-        results.add(Pair.of(ZScan, buildArgs("key1", "0", "MATCH", "*", "COUNT", "10")));
-
         transaction.hscan("key1", "0");
         results.add(Pair.of(HScan, buildArgs("key1", "0")));
 
         transaction.hscan("key1", "0", HScanOptions.builder().matchPattern("*").count(10L).build());
-        results.add(Pair.of(HScan, buildArgs("key1", "0", "MATCH", "*", "COUNT", "10")));
+        results.add(
+                Pair.of(
+                        HScan,
+                        buildArgs(
+                                "key1",
+                                "0",
+                                HScanOptions.MATCH_OPTION_STRING,
+                                "*",
+                                HScanOptions.COUNT_OPTION_STRING,
+                                "10")));
 
         var protobufTransaction = transaction.getProtobufTransaction().build();