Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Java: Add SSCAN command #394

Merged
merged 20 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
*
GumpacG marked this conversation as resolved.
Show resolved Hide resolved
* @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.
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
*
* @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 {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just looking ahead to implementing HSCAN, ZSCAN, and SCAN.

/** <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;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<3


requires com.google.protobuf;
requires io.netty.codec;
Expand Down
Loading
Loading