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 ZSCAN command #397

Merged
merged 35 commits into from
Jun 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
99679c1
WIP TODO: support transactions, docs, and more IT
GumpacG Jun 24, 2024
cae15ba
Added more tests
GumpacG Jun 25, 2024
e2d4e65
Added tests and javadocs
GumpacG Jun 27, 2024
f8efe80
Improved examples and tests
GumpacG Jun 27, 2024
cfff932
Correct use of SScanOptions instead of ScanOptions for SScan
jduo Jun 27, 2024
30065ac
Remove plumbing for SCAN command
jduo Jun 27, 2024
3c3c29e
Apply PR comments
jduo Jun 27, 2024
9ee590d
Add better logging for set comparisons
jduo Jun 28, 2024
b002a55
Spotless
jduo Jun 28, 2024
d4f3e98
Sleep after sadd() calls before sscan() calls
jduo Jun 28, 2024
c371ea5
Change sscan cursor to be a String
jduo Jun 28, 2024
080fcff
WIP with todos
GumpacG Jun 27, 2024
1f50681
Add docs and examples for ZScanOptions overload
jduo Jun 27, 2024
1d78f1e
Fix the examples
jduo Jun 27, 2024
0c31917
Add ZScan to TransactionTestUtilities
jduo Jun 27, 2024
593f6ec
Add docs to BaseTransaction
jduo Jun 27, 2024
7d8158b
Update license header on ZScanOptions
jduo Jun 27, 2024
94a44af
Spotless cleanup
jduo Jun 27, 2024
fc6d0e6
Test fixes
jduo Jun 28, 2024
dad7011
Cleanup test code
jduo Jun 28, 2024
0727e5e
Clarify the layout of the content array in zscan
jduo Jun 28, 2024
ee87fbe
Added better error info for set comparison failures
jduo Jun 28, 2024
bebbba4
More logging for test failures
jduo Jun 28, 2024
5c5df90
Add sleeps after zadd() calls
jduo Jun 28, 2024
6217f9c
Longer sleeps
jduo Jun 28, 2024
e9f25b2
Reduce wait time
jduo Jun 28, 2024
31e517f
Experiment with unsigned 64-bit cursors
jduo Jun 28, 2024
42496e5
Test bugfixes
jduo Jun 28, 2024
3c23e8b
Update examples for cursor change to String
jduo Jun 28, 2024
bbd9915
Correct documentation for String cursor
jduo Jun 28, 2024
96cf8e3
Tweak casing of zScanOptions
jduo Jun 28, 2024
34ab37f
Non-nullable cursors
jduo Jun 28, 2024
b6628c6
Fix rebase error
jduo Jun 28, 2024
0fe01bd
Address PR feedback
jduo Jun 28, 2024
92370e7
Fix rebase conflict
jduo Jun 29, 2024
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 @@ -238,6 +238,7 @@ enum RequestType {
FunctionRestore = 197;
XPending = 198;
SScan = 199;
ZScan = 200;
}

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 @@ -208,6 +208,7 @@ pub enum RequestType {
FunctionRestore = 197,
XPending = 198,
SScan = 199,
ZScan = 200,
}

fn get_two_word_command(first: &str, second: &str) -> Cmd {
Expand Down Expand Up @@ -419,6 +420,7 @@ impl From<::protobuf::EnumOrUnknown<ProtobufRequestType>> for RequestType {
ProtobufRequestType::FunctionRestore => RequestType::FunctionRestore,
ProtobufRequestType::XPending => RequestType::XPending,
ProtobufRequestType::SScan => RequestType::SScan,
ProtobufRequestType::ZScan => RequestType::ZScan,
}
}
}
Expand Down Expand Up @@ -628,6 +630,7 @@ impl RequestType {
RequestType::FunctionRestore => Some(get_two_word_command("FUNCTION", "RESTORE")),
RequestType::XPending => Some(cmd("XPENDING")),
RequestType::SScan => Some(cmd("SSCAN")),
RequestType::ZScan => Some(cmd("ZSCAN")),
}
}
}
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 @@ -167,6 +167,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 @@ -209,6 +210,7 @@
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 @@ -2799,4 +2801,17 @@ public CompletableFuture<Object[]> sscan(
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);
}
}
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,73 @@ 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 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>. The array in the second
* element is always a flattened series of String 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 set.
* @param cursor The cursor that points to the next iteration of results.
* @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 set. The second element is always an <code>
* Array</code> of the subset of the set held in <code>key</code>. The array in the second
* element is always a flattened series of String 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);
}
41 changes: 41 additions & 0 deletions java/client/src/main/java/glide/api/models/BaseTransaction.java
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,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 @@ -242,6 +243,7 @@
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.StreamAddOptions.StreamAddOptionsBuilder;
import glide.api.models.commands.stream.StreamGroupOptions;
Expand Down Expand Up @@ -5163,6 +5165,45 @@ public T sscan(@NonNull String key, @NonNull String cursor, @NonNull SScanOption
return getThis();
}

/**
* 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 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>. The array
* in the second element is always a flattened series of String pairs, where the value is at
* even indices and the score is at odd indices.
*/
public T zscan(@NonNull String key, @NonNull String cursor) {
protobufTransaction.addCommands(buildCommand(ZScan, buildArgs(key, cursor)));
return getThis();
}

/**
* 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 set.
* @param cursor The cursor that points to the next iteration of results.
* @param zScanOptions The {@link ZScanOptions}.
* @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>. The array
* in the second element is always a flattened series of String pairs, where the value is at
* even indices and the score is at odd indices.
*/
public T zscan(@NonNull String key, @NonNull String cursor, @NonNull ZScanOptions zScanOptions) {
ArgsArray commandArgs =
buildArgs(concatenateArrays(new String[] {key, cursor}, zScanOptions.toArgs()));
protobufTransaction.addCommands(buildCommand(ZScan, 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.SortedSetBaseCommands;
import lombok.experimental.SuperBuilder;

/**
* Optional arguments for {@link SortedSetBaseCommands#zscan(String, String, ZScanOptions)}.
*
* @see <a href="https://valkey.io/commands/zscan/">valkey.io</a>
*/
@SuperBuilder
public class ZScanOptions extends ScanOptions {}
54 changes: 54 additions & 0 deletions java/client/src/test/java/glide/api/RedisClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,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 @@ -298,6 +299,7 @@
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 @@ -9013,4 +9015,56 @@ public void sscan_with_options_returns_success() {
assertEquals(testResponse, response);
assertEquals(value, payload);
}

@SneakyThrows
@Test
public void zscan_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<Object[]> testResponse = new CompletableFuture<>();
testResponse.complete(value);

// match on protobuf request
when(commandManager.<Object[]>submitNewCommand(eq(ZScan), eq(arguments), any()))
.thenReturn(testResponse);

// exercise
CompletableFuture<Object[]> response = service.zscan(key, cursor);
Object[] payload = response.get();

// verify
assertEquals(testResponse, response);
assertEquals(value, payload);
}

@SneakyThrows
@Test
public void zscan_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<Object[]> testResponse = new CompletableFuture<>();
testResponse.complete(value);

// match on protobuf request
when(commandManager.<Object[]>submitNewCommand(eq(ZScan), eq(arguments), any()))
.thenReturn(testResponse);

// exercise
CompletableFuture<Object[]> response =
service.zscan(key, cursor, ZScanOptions.builder().matchPattern("*").count(1L).build());
Object[] payload = response.get();

// verify
assertEquals(testResponse, response);
assertEquals(value, payload);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,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 @@ -249,6 +250,7 @@
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 @@ -1176,6 +1178,12 @@ InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3, false), new Limit(1, 2)),
transaction.sscan("key1", "0", SScanOptions.builder().matchPattern("*").count(10L).build());
results.add(Pair.of(SScan, buildArgs("key1", "0", "MATCH", "*", "COUNT", "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++) {
Expand Down
Loading
Loading