Skip to content

Commit

Permalink
Java: Add SSCAN command (#394)
Browse files Browse the repository at this point in the history
* 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 <james.duong@improving.com>
  • Loading branch information
GumpacG and jduo authored Jun 28, 2024
1 parent 4b12c86 commit 49c4cb0
Show file tree
Hide file tree
Showing 12 changed files with 433 additions and 51 deletions.
1 change: 1 addition & 0 deletions glide-core/src/protobuf/redis_request.proto
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ enum RequestType {
FunctionDump = 196;
FunctionRestore = 197;
XPending = 198;
SScan = 199;
}

message Command {
Expand Down
3 changes: 3 additions & 0 deletions glide-core/src/request_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ pub enum RequestType {
FunctionDump = 196,
FunctionRestore = 197,
XPending = 198,
SScan = 199,
}

fn get_two_word_command(first: &str, second: &str) -> Cmd {
Expand Down Expand Up @@ -417,6 +418,7 @@ impl From<::protobuf::EnumOrUnknown<ProtobufRequestType>> for RequestType {
ProtobufRequestType::FunctionDump => RequestType::FunctionDump,
ProtobufRequestType::FunctionRestore => RequestType::FunctionRestore,
ProtobufRequestType::XPending => RequestType::XPending,
ProtobufRequestType::SScan => RequestType::SScan,
}
}
}
Expand Down Expand Up @@ -625,6 +627,7 @@ impl RequestType {
RequestType::FunctionDump => Some(get_two_word_command("FUNCTION", "DUMP")),
RequestType::FunctionRestore => Some(get_two_word_command("FUNCTION", "RESTORE")),
RequestType::XPending => Some(cmd("XPENDING")),
RequestType::SScan => Some(cmd("SSCAN")),
}
}
}
15 changes: 15 additions & 0 deletions java/client/src/main/java/glide/api/BaseClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,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 @@ -207,6 +208,7 @@
import glide.api.models.commands.geospatial.GeoAddOptions;
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.stream.StreamAddOptions;
import glide.api.models.commands.stream.StreamGroupOptions;
import glide.api.models.commands.stream.StreamPendingOptions;
Expand Down Expand Up @@ -2784,4 +2786,17 @@ public CompletableFuture<Long> sortStore(@NonNull String key, @NonNull String de
return commandManager.submitNewCommand(
Sort, new String[] {key, STORE_COMMAND_STRING, destination}, 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);
}
}
56 changes: 56 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,59 @@ 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.
* @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.
* @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);
}
139 changes: 88 additions & 51 deletions java/client/src/main/java/glide/api/models/BaseTransaction.java
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,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 @@ -240,6 +241,7 @@
import glide.api.models.commands.geospatial.GeoAddOptions;
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.stream.StreamAddOptions;
import glide.api.models.commands.stream.StreamAddOptions.StreamAddOptionsBuilder;
import glide.api.models.commands.stream.StreamGroupOptions;
Expand Down Expand Up @@ -4876,57 +4878,6 @@ public T sunion(@NonNull String[] keys) {
return getThis();
}

/**
* Sorts the elements in the list, set, or sorted set at <code>key</code> and returns the result.
* <br>
* The <code>sort</code> command can be used to sort elements based on different criteria and
* apply transformations on sorted elements.<br>
* To store the result into a new key, see {@link #sortStore(String, String)}.<br>
*
* @param key The key of the list, set, or sorted set to be sorted.
* @return Command Response - An <code>Array</code> of sorted elements.
*/
public T sort(@NonNull String key) {
ArgsArray commandArgs = buildArgs(key);
protobufTransaction.addCommands(buildCommand(Sort, commandArgs));
return getThis();
}

/**
* Sorts the elements in the list, set, or sorted set at <code>key</code> and returns the result.
* <br>
* The <code>sortReadOnly</code> 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 <code>Array</code> of sorted elements.
*/
public T sortReadOnly(@NonNull String key) {
ArgsArray commandArgs = buildArgs(key);
protobufTransaction.addCommands(buildCommand(SortReadOnly, commandArgs));
return getThis();
}

/**
* Sorts the elements in the list, set, or sorted set at <code>key</code> and stores the result in
* <code>destination</code>. The <code>sort</code> command can be used to sort elements based on
* different criteria, apply transformations on sorted elements, and store the result in a new
* key.<br>
* 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 <code>destination
* </code>.
*/
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 indices and length of the longest common subsequence between strings stored at
* <code>key1</code> and <code>key2</code>.
Expand Down Expand Up @@ -5126,6 +5077,92 @@ public T lcsIdxWithMatchLen(@NonNull String key1, @NonNull String key2, long min
return getThis();
}

/**
* Sorts the elements in the list, set, or sorted set at <code>key</code> and returns the result.
* <br>
* The <code>sort</code> command can be used to sort elements based on different criteria and
* apply transformations on sorted elements.<br>
* To store the result into a new key, see {@link #sortStore(String, String)}.<br>
*
* @param key The key of the list, set, or sorted set to be sorted.
* @return Command Response - An <code>Array</code> of sorted elements.
*/
public T sort(@NonNull String key) {
ArgsArray commandArgs = buildArgs(key);
protobufTransaction.addCommands(buildCommand(Sort, commandArgs));
return getThis();
}

/**
* Sorts the elements in the list, set, or sorted set at <code>key</code> and returns the result.
* <br>
* The <code>sortReadOnly</code> 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 <code>Array</code> of sorted elements.
*/
public T sortReadOnly(@NonNull String key) {
ArgsArray commandArgs = buildArgs(key);
protobufTransaction.addCommands(buildCommand(SortReadOnly, commandArgs));
return getThis();
}

/**
* Sorts the elements in the list, set, or sorted set at <code>key</code> and stores the result in
* <code>destination</code>. The <code>sort</code> command can be used to sort elements based on
* different criteria, apply transformations on sorted elements, and store the result in a new
* key.<br>
* 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 <code>destination
* </code>.
*/
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();
}

/**
* 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.
* @return Command Response - 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>.
*/
public T sscan(@NonNull String key, @NonNull String cursor) {
protobufTransaction.addCommands(buildCommand(SScan, buildArgs(key, cursor)));
return getThis();
}

/**
* 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.
* @param sScanOptions The {@link SScanOptions}.
* @return Command Response - 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>.
*/
public T sscan(@NonNull String key, @NonNull String cursor, @NonNull SScanOptions sScanOptions) {
ArgsArray commandArgs =
buildArgs(concatenateArrays(new String[] {key, cursor}, sScanOptions.toArgs()));
protobufTransaction.addCommands(buildCommand(SScan, commandArgs));
return getThis();
}

/** Build protobuf {@link Command} object for given command and arguments. */
protected Command buildCommand(RequestType requestType) {
return buildCommand(requestType, buildArgs());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */
package glide.api.models.commands.scan;

import glide.api.commands.SetBaseCommands;
import lombok.experimental.SuperBuilder;

/**
* Optional arguments for {@link SetBaseCommands#sscan(String, String, SScanOptions)}.
*
* @see <a href="https://valkey.io/commands/sscan/">valkey.io</a>
*/
@SuperBuilder
public class SScanOptions extends ScanOptions {}
Original file line number Diff line number Diff line change
@@ -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 {
/** <code>MATCH</code> option string to include in the <code>SCAN</code> commands. */
public static final String MATCH_OPTION_STRING = "MATCH";

/** <code>COUNT</code> option string to include in the <code>SCAN</code> 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
* <code>COUNT</code> being <code>10</code> which indicates that it will only fetch and match
* <code>10</code> items from the list.
*/
private final String matchPattern;

/**
* <code>COUNT</code> is a just a hint for the command for how many elements to fetch from the
* set, hash, or list. <code>COUNT</code> could be ignored until the set, hash, or list is large
* enough for the <code>SCAN</code> commands to represent the results as compact single-allocation
* packed encoding.
*/
private final Long count;

/**
* Creates the arguments to be used in <code>SCAN</code> commands.
*
* @return a String array that holds the options and their arguments.
*/
public String[] toArgs() {
List<String> 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]);
}
}
1 change: 1 addition & 0 deletions java/client/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
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;
Expand Down
Loading

0 comments on commit 49c4cb0

Please sign in to comment.