Skip to content

Commit

Permalink
Java: Add SSCAN and ZSCAN commands (valkey-io#1705)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

* Java: Add `ZSCAN` command (#397)

---------

Co-authored-by: James Duong <[email protected]>

* WIP TODO: support transactions, docs, and more IT

* Added more tests

* Added tests and javadocs

* Improved examples and tests

* Correct use of SScanOptions instead of ScanOptions for SScan

* Remove plumbing for SCAN command

* Sleep after sadd() calls before sscan() calls

Due to eventual consistency

* Change sscan cursor to be a String

Also fix bug in SharedCommandTests

* 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

* Add ZScan to TransactionTestUtilities

* Spotless cleanup

* Test fixes

* Cleanup test code

* Apply IntelliJ suggestions
* Use String.valueOf() instead of concatenating empty string

* Added better error info for set comparison failures

* More logging for test failures

* Add sleeps after zadd() calls

To help make sure data is consistent without WAIT

* Longer sleeps

* Reduce wait time

* Experiment with unsigned 64-bit cursors

* Fix rebase error

* WIP TODO: support transactions, docs, and more IT

* Added more tests

* Added tests and javadocs

* Improved examples and tests

* Apply PR comments

* Fix method ordering in BaseTransaction
* Fix broken line breaks within code tags in ScanOptions
* More thoroughly test results in SharedCommandTests

* Add better logging for set comparisons

* Spotless

* Sleep after sadd() calls before sscan() calls

Due to eventual consistency

* Change sscan cursor to be a String

Also fix bug in SharedCommandTests

* Update java/integTest/src/test/java/glide/SharedCommandTests.java

Co-authored-by: Guian Gumpac <[email protected]>

* Update java/integTest/src/test/java/glide/SharedCommandTests.java

Co-authored-by: Guian Gumpac <[email protected]>

* Fix rebase conflicts

* Fix another rebase conflict

* Spotless

* Update java/client/src/main/java/glide/api/models/BaseTransaction.java

Co-authored-by: Andrew Carbonetto <[email protected]>

* Update java/client/src/main/java/glide/api/models/BaseTransaction.java

Co-authored-by: Andrew Carbonetto <[email protected]>

* Update java/client/src/main/java/glide/api/models/BaseTransaction.java

Co-authored-by: Andrew Carbonetto <[email protected]>

* Update java/client/src/main/java/glide/api/models/BaseTransaction.java

Co-authored-by: Andrew Carbonetto <[email protected]>

* Correctly use constants in TransactionTests

* Rename ScanOptions to BaseScanOptions

* Doc PR fixes

* Treat end of cursor as failure

* Spotless

* Fixes

* Update java/client/src/main/java/glide/api/commands/SortedSetBaseCommands.java

Co-authored-by: Andrew Carbonetto <[email protected]>

* Update java/client/src/main/java/glide/api/commands/SortedSetBaseCommands.java

Co-authored-by: Andrew Carbonetto <[email protected]>

* Update java/client/src/main/java/glide/api/commands/SortedSetBaseCommands.java

Co-authored-by: Andrew Carbonetto <[email protected]>

* Update java/client/src/main/java/glide/api/commands/SortedSetBaseCommands.java

Co-authored-by: Andrew Carbonetto <[email protected]>

* Minor doc changes

---------

Co-authored-by: Guian Gumpac <[email protected]>
Co-authored-by: Andrew Carbonetto <[email protected]>
  • Loading branch information
3 people authored and cyip10 committed Jul 16, 2024
1 parent 977e680 commit 0744a30
Show file tree
Hide file tree
Showing 14 changed files with 872 additions and 51 deletions.
2 changes: 2 additions & 0 deletions glide-core/src/protobuf/redis_request.proto
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@ enum RequestType {
FunctionRestore = 197;
XPending = 198;
XGroupSetId = 199;
SScan = 200;
ZScan = 201;
}

message Command {
Expand Down
6 changes: 6 additions & 0 deletions glide-core/src/request_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,8 @@ pub enum RequestType {
FunctionRestore = 197,
XPending = 198,
XGroupSetId = 199,
SScan = 200,
ZScan = 201,
}

fn get_two_word_command(first: &str, second: &str) -> Cmd {
Expand Down Expand Up @@ -419,6 +421,8 @@ impl From<::protobuf::EnumOrUnknown<ProtobufRequestType>> for RequestType {
ProtobufRequestType::FunctionRestore => RequestType::FunctionRestore,
ProtobufRequestType::XPending => RequestType::XPending,
ProtobufRequestType::XGroupSetId => RequestType::XGroupSetId,
ProtobufRequestType::SScan => RequestType::SScan,
ProtobufRequestType::ZScan => RequestType::ZScan,
}
}
}
Expand Down Expand Up @@ -628,6 +632,8 @@ impl RequestType {
RequestType::FunctionRestore => Some(get_two_word_command("FUNCTION", "RESTORE")),
RequestType::XPending => Some(cmd("XPENDING")),
RequestType::XGroupSetId => Some(get_two_word_command("XGROUP", "SETID")),
RequestType::SScan => Some(cmd("SSCAN")),
RequestType::ZScan => Some(cmd("ZSCAN")),
}
}
}
30 changes: 30 additions & 0 deletions java/client/src/main/java/glide/api/BaseClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.SPop;
import static redis_request.RedisRequestOuterClass.RequestType.SRandMember;
import static redis_request.RedisRequestOuterClass.RequestType.SRem;
import static redis_request.RedisRequestOuterClass.RequestType.SScan;
import static redis_request.RedisRequestOuterClass.RequestType.SUnion;
import static redis_request.RedisRequestOuterClass.RequestType.SUnionStore;
import static redis_request.RedisRequestOuterClass.RequestType.Set;
Expand Down Expand Up @@ -167,6 +168,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.ZRemRangeByRank;
import static redis_request.RedisRequestOuterClass.RequestType.ZRemRangeByScore;
import static redis_request.RedisRequestOuterClass.RequestType.ZRevRank;
import static redis_request.RedisRequestOuterClass.RequestType.ZScan;
import static redis_request.RedisRequestOuterClass.RequestType.ZScore;
import static redis_request.RedisRequestOuterClass.RequestType.ZUnion;
import static redis_request.RedisRequestOuterClass.RequestType.ZUnionStore;
Expand Down Expand Up @@ -213,6 +215,8 @@
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.SScanOptions;
import glide.api.models.commands.scan.ZScanOptions;
import glide.api.models.commands.stream.StreamAddOptions;
import glide.api.models.commands.stream.StreamGroupOptions;
import glide.api.models.commands.stream.StreamPendingOptions;
Expand Down Expand Up @@ -2905,4 +2909,30 @@ public CompletableFuture<Long> geosearchstore(
resultOptions.toArgs());
return commandManager.submitNewCommand(GeoSearchStore, arguments, this::handleLongResponse);
}

@Override
public CompletableFuture<Object[]> sscan(@NonNull String key, @NonNull String cursor) {
String[] arguments = new String[] {key, cursor};
return commandManager.submitNewCommand(SScan, arguments, this::handleArrayResponse);
}

@Override
public CompletableFuture<Object[]> sscan(
@NonNull String key, @NonNull String cursor, @NonNull SScanOptions sScanOptions) {
String[] arguments = concatenateArrays(new String[] {key, cursor}, sScanOptions.toArgs());
return commandManager.submitNewCommand(SScan, arguments, this::handleArrayResponse);
}

@Override
public CompletableFuture<Object[]> zscan(@NonNull String key, @NonNull String cursor) {
String[] arguments = new String[] {key, cursor};
return commandManager.submitNewCommand(ZScan, arguments, this::handleArrayResponse);
}

@Override
public CompletableFuture<Object[]> zscan(
@NonNull String key, @NonNull String cursor, @NonNull ZScanOptions zScanOptions) {
String[] arguments = concatenateArrays(new String[] {key, cursor}, zScanOptions.toArgs());
return commandManager.submitNewCommand(ZScan, arguments, this::handleArrayResponse);
}
}
58 changes: 58 additions & 0 deletions java/client/src/main/java/glide/api/commands/SetBaseCommands.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package glide.api.commands;

import glide.api.models.GlideString;
import glide.api.models.commands.scan.SScanOptions;
import java.util.Set;
import java.util.concurrent.CompletableFuture;

Expand Down Expand Up @@ -553,4 +554,61 @@ public interface SetBaseCommands {
* }</pre>
*/
CompletableFuture<Set<String>> sunion(String[] keys);

/**
* Iterates incrementally over a set.
*
* @see <a href="https://valkey.io/commands/sscan">valkey.io</a> 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.
* @return An <code>Array</code> of <code>Objects</code>. The first element is always the <code>
* cursor</code> for the next iteration of results. <code>0</code> will be the <code>cursor
* </code> returned on the last iteration of the set. The second element is always an <code>
* Array</code> of the subset of the set held in <code>key</code>.
* @example
* <pre>{@code
* // Assume key contains a set with 200 members
* String cursor = "0";
* Object[] result;
* do {
* result = client.sscan(key1, cursor).get();
* cursor = result[0].toString();
* Object[] stringResults = (Object[]) result[1];
*
* System.out.println("\nSSCAN iteration:");
* Arrays.asList(stringResults).stream().forEach(i -> System.out.print(i + ", "));
* } while (!cursor.equals("0"));
* }</pre>
*/
CompletableFuture<Object[]> sscan(String key, String cursor);

/**
* Iterates incrementally over a set.
*
* @see <a href="https://valkey.io/commands/sscan">valkey.io</a> 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 sScanOptions The {@link SScanOptions}.
* @return An <code>Array</code> of <code>Objects</code>. The first element is always the <code>
* cursor</code> for the next iteration of results. <code>0</code> will be the <code>cursor
* </code> returned on the last iteration of the set. The second element is always an <code>
* Array</code> of the subset of the set held in <code>key</code>.
* @example
* <pre>{@code
* // Assume key contains a set with 200 members
* String cursor = "0";
* Object[] result;
* do {
* result = client.sscan(key1, cursor, SScanOptions.builder().matchPattern("*").count(20L).build()).get();
* cursor = result[0].toString();
* Object[] stringResults = (Object[]) result[1];
*
* System.out.println("\nSSCAN iteration:");
* Arrays.asList(stringResults).stream().forEach(i -> System.out.print(i + ", "));
* } while (!cursor.equals("0"));
* }</pre>
*/
CompletableFuture<Object[]> sscan(String key, String cursor, SScanOptions sScanOptions);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import glide.api.models.commands.WeightAggregateOptions.KeysOrWeightedKeys;
import glide.api.models.commands.WeightAggregateOptions.WeightedKeys;
import glide.api.models.commands.ZAddOptions;
import glide.api.models.commands.scan.ZScanOptions;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

Expand Down Expand Up @@ -1577,4 +1578,77 @@ CompletableFuture<Map<String, Double>> zinterWithScores(
* }</pre>
*/
CompletableFuture<Long> zintercard(GlideString[] keys, long limit);

/**
* Iterates incrementally over a sorted set.
*
* @see <a href="https://valkey.io/commands/zscan">valkey.io</a> 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.
* @return An <code>Array</code> of <code>Objects</code>. The first element is always the <code>
* cursor</code> for the next iteration of results. <code>0</code> will be the <code>cursor
* </code> returned on the last iteration of the sorted set. The second element is always an
* <code>
* Array</code> of the subset of the sorted set held in <code>key</code>. The array in the
* second element is always a flattened series of <code>String</code> pairs, where the value
* is at even indices and the score is at odd indices.
* @example
* <pre>{@code
* // Assume key contains a set with 200 member-score pairs
* String cursor = "0";
* Object[] result;
* do {
* result = client.zscan(key1, cursor).get();
* cursor = result[0].toString();
* Object[] stringResults = (Object[]) result[1];
*
* System.out.println("\nZSCAN 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"));
* }</pre>
*/
CompletableFuture<Object[]> zscan(String key, String cursor);

/**
* Iterates incrementally over a sorted set.
*
* @see <a href="https://valkey.io/commands/zscan">valkey.io</a> 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 zScanOptions The {@link ZScanOptions}.
* @return An <code>Array</code> of <code>Objects</code>. The first element is always the <code>
* cursor</code> for the next iteration of results. <code>0</code> will be the <code>cursor
* </code> returned on the last iteration of the sorted set. The second element is always an
* <code>
* Array</code> of the subset of the sorted set held in <code>key</code>. The array in the
* second element is always a flattened series of <code>String</code> pairs, where the value
* is at even indices and the score is at odd indices.
* @example
* <pre>{@code
* // Assume key contains a set with 200 member-score pairs
* String cursor = "0";
* Object[] result;
* do {
* result = client.zscan(key1, cursor, ZScanOptions.builder().matchPattern("*").count(20L).build()).get();
* cursor = result[0].toString();
* Object[] stringResults = (Object[]) result[1];
*
* System.out.println("\nZSCAN 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"));
* }</pre>
*/
CompletableFuture<Object[]> zscan(String key, String cursor, ZScanOptions zScanOptions);
}
Loading

0 comments on commit 0744a30

Please sign in to comment.