Skip to content

Commit

Permalink
Java: Add HSCAN command (valkey-io#1706)
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

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

Due to eventual consistency

* Change sscan cursor to be a String

Also fix bug in SharedCommandTests

* Fix rebase conflicts

* Fix another rebase conflict

* Spotless

* HScan

* Flakey test

* Add HScan transaction unit test

* Rename ScanOptions to BaseScanOptions

* Fix merge issues

* Fix module-info ordering

* Tidy up docs

* PR comments

Fix up merge duplication and use HScanOptions constants.

---------

Co-authored-by: Guian Gumpac <[email protected]>
  • Loading branch information
2 people authored and cyip10 committed Jul 16, 2024
1 parent c24d7ee commit 6b37715
Show file tree
Hide file tree
Showing 13 changed files with 422 additions and 42 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 @@ -240,6 +240,7 @@ enum RequestType {
XGroupSetId = 199;
SScan = 200;
ZScan = 201;
HScan = 202;
}

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 @@ -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 {
Expand Down Expand Up @@ -423,6 +424,7 @@ impl From<::protobuf::EnumOrUnknown<ProtobufRequestType>> for RequestType {
ProtobufRequestType::XGroupSetId => RequestType::XGroupSetId,
ProtobufRequestType::SScan => RequestType::SScan,
ProtobufRequestType::ZScan => RequestType::ZScan,
ProtobufRequestType::HScan => RequestType::HScan,
}
}
}
Expand Down Expand Up @@ -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")),
}
}
}
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 @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -2935,4 +2937,17 @@ public CompletableFuture<Object[]> zscan(
String[] arguments = concatenateArrays(new String[] {key, cursor}, zScanOptions.toArgs());
return commandManager.submitNewCommand(ZScan, arguments, this::handleArrayResponse);
}

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

@Override
public CompletableFuture<Object[]> 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);
}
}
72 changes: 72 additions & 0 deletions java/client/src/main/java/glide/api/commands/HashBaseCommands.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.HScanOptions;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

Expand Down Expand Up @@ -432,4 +433,75 @@ public interface HashBaseCommands {
* }</pre>
*/
CompletableFuture<String[][]> hrandfieldWithCountWithValues(String key, long count);

/**
* Iterates fields of Hash types and their associated values.
*
* @see <a href="https://valkey.io/commands/hscan">valkey.io</a> for details.
* @param key The key of the hash.
* @param cursor The cursor that points to the next iteration of results. A value of <code>"0"
* </code> 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 result. The second element is always an
* <code>Array</code> of the subset of the hash held in <code>key</code>. The array in the
* second element is always a flattened series of <code>String</code> pairs, where the key is
* at even indices and the value 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.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"));
* }</pre>
*/
CompletableFuture<Object[]> hscan(String key, String cursor);

/**
* Iterates fields of Hash types and their associated values.
*
* @see <a href="https://valkey.io/commands/hscan">valkey.io</a> for details.
* @param key The key of the hash.
* @param cursor The cursor that points to the next iteration of results. A value of <code>"0"
* </code> indicates the start of the search.
* @param hScanOptions The {@link HScanOptions}.
* @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 result. The second element is always an
* <code>Array</code> of the subset of the hash held in <code>key</code>. The array in the
* second element is always a flattened series of <code>String</code> pairs, where the key is
* at even indices and the value 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.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"));
* }</pre>
*/
CompletableFuture<Object[]> hscan(String key, String cursor, HScanOptions hScanOptions);
}
Original file line number Diff line number Diff line change
Expand Up @@ -560,10 +560,10 @@ public interface SetBaseCommands {
*
* @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 cursor The cursor that points to the next iteration of results. A value of <code>"0"
* </code> 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
* 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
Expand All @@ -588,11 +588,11 @@ public interface SetBaseCommands {
*
* @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 cursor The cursor that points to the next iteration of results. A value of <code>"0"
* </code> 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
* 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1584,10 +1584,10 @@ CompletableFuture<Map<String, Double>> zinterWithScores(
*
* @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 cursor The cursor that points to the next iteration of results. A value of <code>"0"
* </code> 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
* 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
Expand Down Expand Up @@ -1620,11 +1620,11 @@ CompletableFuture<Map<String, Double>> zinterWithScores(
*
* @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 cursor The cursor that points to the next iteration of results. A value of <code>"0"
* </code> 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
* 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
Expand Down
67 changes: 55 additions & 12 deletions java/client/src/main/java/glide/api/models/BaseTransaction.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -5509,10 +5511,10 @@ public T geosearchstore(
*
* @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 cursor The cursor that points to the next iteration of results. A value of <code>"0"
* </code> indicates the start of the search.
* @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
* 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>.
*/
Expand All @@ -5526,11 +5528,11 @@ public T sscan(@NonNull String key, @NonNull String cursor) {
*
* @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 cursor The cursor that points to the next iteration of results. A value of <code>"0"
* </code> indicates the start of the search.
* @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
* 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>.
*/
Expand All @@ -5546,10 +5548,10 @@ public T sscan(@NonNull String key, @NonNull String cursor, @NonNull SScanOption
*
* @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 cursor The cursor that points to the next iteration of results. A value of <code>"0"
* </code> indicates the start of the search.
* @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
* 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
Expand All @@ -5565,11 +5567,11 @@ public T zscan(@NonNull String key, @NonNull String cursor) {
*
* @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 cursor The cursor that points to the next iteration of results. A value of <code>"0"
* </code> indicates the start of the search.
* @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
* 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
Expand All @@ -5582,6 +5584,47 @@ public T zscan(@NonNull String key, @NonNull String cursor, @NonNull ZScanOption
return getThis();
}

/**
* Iterates fields of Hash types and their associated values.
*
* @see <a href="https://valkey.io/commands/hscan">valkey.io</a> for details.
* @param key The key of the hash.
* @param cursor The cursor that points to the next iteration of results. A value of <code>"0"
* </code> indicates the start of the search.
* @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 result. The second element is
* always an <code>Array</code> of the subset of the hash held in <code>key</code>. The array
* in the second element is always a flattened series of <code>String</code> 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 <a href="https://valkey.io/commands/hscan">valkey.io</a> for details.
* @param key The key of the hash.
* @param cursor The cursor that points to the next iteration of results. A value of <code>"0"
* </code> indicates the start of the search.
* @param hScanOptions The {@link HScanOptions}.
* @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 result. The second element is
* always an <code>Array</code> of the subset of the hash held in <code>key</code>. The array
* in the second element is always a flattened series of <code>String</code> 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());
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.HashBaseCommands;
import lombok.experimental.SuperBuilder;

/**
* Optional arguments for {@link HashBaseCommands#hscan(String, String, HScanOptions)}.
*
* @see <a href="https://valkey.io/commands/hscan/">valkey.io</a>
*/
@SuperBuilder
public class HScanOptions extends BaseScanOptions {}
2 changes: 1 addition & 1 deletion java/client/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading

0 comments on commit 6b37715

Please sign in to comment.