Skip to content

Commit c230d5c

Browse files
jduoGumpacG
andauthored
Java: Add HSCAN command (#1706)
* 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 <james.duong@improving.com> * Java: Add `ZSCAN` command (#397) --------- Co-authored-by: James Duong <james.duong@improving.com> * 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 <guian.gumpac@improving.com>
1 parent dd38024 commit c230d5c

13 files changed

Lines changed: 422 additions & 42 deletions

File tree

glide-core/src/protobuf/redis_request.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ enum RequestType {
240240
XGroupSetId = 199;
241241
SScan = 200;
242242
ZScan = 201;
243+
HScan = 202;
243244
}
244245

245246
message Command {

glide-core/src/request_type.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ pub enum RequestType {
210210
XGroupSetId = 199,
211211
SScan = 200,
212212
ZScan = 201,
213+
HScan = 202,
213214
}
214215

215216
fn get_two_word_command(first: &str, second: &str) -> Cmd {
@@ -423,6 +424,7 @@ impl From<::protobuf::EnumOrUnknown<ProtobufRequestType>> for RequestType {
423424
ProtobufRequestType::XGroupSetId => RequestType::XGroupSetId,
424425
ProtobufRequestType::SScan => RequestType::SScan,
425426
ProtobufRequestType::ZScan => RequestType::ZScan,
427+
ProtobufRequestType::HScan => RequestType::HScan,
426428
}
427429
}
428430
}
@@ -634,6 +636,7 @@ impl RequestType {
634636
RequestType::XGroupSetId => Some(get_two_word_command("XGROUP", "SETID")),
635637
RequestType::SScan => Some(cmd("SSCAN")),
636638
RequestType::ZScan => Some(cmd("ZSCAN")),
639+
RequestType::HScan => Some(cmd("HSCAN")),
637640
}
638641
}
639642
}

java/client/src/main/java/glide/api/BaseClient.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
import static redis_request.RedisRequestOuterClass.RequestType.HLen;
6262
import static redis_request.RedisRequestOuterClass.RequestType.HMGet;
6363
import static redis_request.RedisRequestOuterClass.RequestType.HRandField;
64+
import static redis_request.RedisRequestOuterClass.RequestType.HScan;
6465
import static redis_request.RedisRequestOuterClass.RequestType.HSet;
6566
import static redis_request.RedisRequestOuterClass.RequestType.HSetNX;
6667
import static redis_request.RedisRequestOuterClass.RequestType.HStrlen;
@@ -215,6 +216,7 @@
215216
import glide.api.models.commands.geospatial.GeoSearchStoreOptions;
216217
import glide.api.models.commands.geospatial.GeoUnit;
217218
import glide.api.models.commands.geospatial.GeospatialData;
219+
import glide.api.models.commands.scan.HScanOptions;
218220
import glide.api.models.commands.scan.SScanOptions;
219221
import glide.api.models.commands.scan.ZScanOptions;
220222
import glide.api.models.commands.stream.StreamAddOptions;
@@ -2935,4 +2937,17 @@ public CompletableFuture<Object[]> zscan(
29352937
String[] arguments = concatenateArrays(new String[] {key, cursor}, zScanOptions.toArgs());
29362938
return commandManager.submitNewCommand(ZScan, arguments, this::handleArrayResponse);
29372939
}
2940+
2941+
@Override
2942+
public CompletableFuture<Object[]> hscan(@NonNull String key, @NonNull String cursor) {
2943+
String[] arguments = new String[] {key, cursor};
2944+
return commandManager.submitNewCommand(HScan, arguments, this::handleArrayResponse);
2945+
}
2946+
2947+
@Override
2948+
public CompletableFuture<Object[]> hscan(
2949+
@NonNull String key, @NonNull String cursor, @NonNull HScanOptions hScanOptions) {
2950+
String[] arguments = concatenateArrays(new String[] {key, cursor}, hScanOptions.toArgs());
2951+
return commandManager.submitNewCommand(HScan, arguments, this::handleArrayResponse);
2952+
}
29382953
}

java/client/src/main/java/glide/api/commands/HashBaseCommands.java

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
package glide.api.commands;
33

44
import glide.api.models.GlideString;
5+
import glide.api.models.commands.scan.HScanOptions;
56
import java.util.Map;
67
import java.util.concurrent.CompletableFuture;
78

@@ -432,4 +433,75 @@ public interface HashBaseCommands {
432433
* }</pre>
433434
*/
434435
CompletableFuture<String[][]> hrandfieldWithCountWithValues(String key, long count);
436+
437+
/**
438+
* Iterates fields of Hash types and their associated values.
439+
*
440+
* @see <a href="https://valkey.io/commands/hscan">valkey.io</a> for details.
441+
* @param key The key of the hash.
442+
* @param cursor The cursor that points to the next iteration of results. A value of <code>"0"
443+
* </code> indicates the start of the search.
444+
* @return An <code>Array</code> of <code>Objects</code>. The first element is always the <code>
445+
* cursor</code> for the next iteration of results. <code>"0"</code> will be the <code>cursor
446+
* </code> returned on the last iteration of the result. The second element is always an
447+
* <code>Array</code> of the subset of the hash held in <code>key</code>. The array in the
448+
* second element is always a flattened series of <code>String</code> pairs, where the key is
449+
* at even indices and the value is at odd indices.
450+
* @example
451+
* <pre>{@code
452+
* // Assume key contains a set with 200 member-score pairs
453+
* String cursor = "0";
454+
* Object[] result;
455+
* do {
456+
* result = client.hscan(key1, cursor).get();
457+
* cursor = result[0].toString();
458+
* Object[] stringResults = (Object[]) result[1];
459+
*
460+
* System.out.println("\nHSCAN iteration:");
461+
* for (int i = 0; i < stringResults.length; i += 2) {
462+
* System.out.printf("{%s=%s}", stringResults[i], stringResults[i + 1]);
463+
* if (i + 2 < stringResults.length) {
464+
* System.out.print(", ");
465+
* }
466+
* }
467+
* } while (!cursor.equals("0"));
468+
* }</pre>
469+
*/
470+
CompletableFuture<Object[]> hscan(String key, String cursor);
471+
472+
/**
473+
* Iterates fields of Hash types and their associated values.
474+
*
475+
* @see <a href="https://valkey.io/commands/hscan">valkey.io</a> for details.
476+
* @param key The key of the hash.
477+
* @param cursor The cursor that points to the next iteration of results. A value of <code>"0"
478+
* </code> indicates the start of the search.
479+
* @param hScanOptions The {@link HScanOptions}.
480+
* @return An <code>Array</code> of <code>Objects</code>. The first element is always the <code>
481+
* cursor</code> for the next iteration of results. <code>"0"</code> will be the <code>cursor
482+
* </code> returned on the last iteration of the result. The second element is always an
483+
* <code>Array</code> of the subset of the hash held in <code>key</code>. The array in the
484+
* second element is always a flattened series of <code>String</code> pairs, where the key is
485+
* at even indices and the value is at odd indices.
486+
* @example
487+
* <pre>{@code
488+
* // Assume key contains a set with 200 member-score pairs
489+
* String cursor = "0";
490+
* Object[] result;
491+
* do {
492+
* result = client.hscan(key1, cursor, HScanOptions.builder().matchPattern("*").count(20L).build()).get();
493+
* cursor = result[0].toString();
494+
* Object[] stringResults = (Object[]) result[1];
495+
*
496+
* System.out.println("\nHSCAN iteration:");
497+
* for (int i = 0; i < stringResults.length; i += 2) {
498+
* System.out.printf("{%s=%s}", stringResults[i], stringResults[i + 1]);
499+
* if (i + 2 < stringResults.length) {
500+
* System.out.print(", ");
501+
* }
502+
* }
503+
* } while (!cursor.equals("0"));
504+
* }</pre>
505+
*/
506+
CompletableFuture<Object[]> hscan(String key, String cursor, HScanOptions hScanOptions);
435507
}

java/client/src/main/java/glide/api/commands/SetBaseCommands.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -560,10 +560,10 @@ public interface SetBaseCommands {
560560
*
561561
* @see <a href="https://valkey.io/commands/sscan">valkey.io</a> for details.
562562
* @param key The key of the set.
563-
* @param cursor The cursor that points to the next iteration of results. A value of 0 indicates
564-
* the start of the search.
563+
* @param cursor The cursor that points to the next iteration of results. A value of <code>"0"
564+
* </code> indicates the start of the search.
565565
* @return An <code>Array</code> of <code>Objects</code>. The first element is always the <code>
566-
* cursor</code> for the next iteration of results. <code>0</code> will be the <code>cursor
566+
* cursor</code> for the next iteration of results. <code>"0"</code> will be the <code>cursor
567567
* </code> returned on the last iteration of the set. The second element is always an <code>
568568
* Array</code> of the subset of the set held in <code>key</code>.
569569
* @example
@@ -588,11 +588,11 @@ public interface SetBaseCommands {
588588
*
589589
* @see <a href="https://valkey.io/commands/sscan">valkey.io</a> for details.
590590
* @param key The key of the set.
591-
* @param cursor The cursor that points to the next iteration of results. A value of 0 indicates
592-
* the start of the search.
591+
* @param cursor The cursor that points to the next iteration of results. A value of <code>"0"
592+
* </code> indicates the start of the search.
593593
* @param sScanOptions The {@link SScanOptions}.
594594
* @return An <code>Array</code> of <code>Objects</code>. The first element is always the <code>
595-
* cursor</code> for the next iteration of results. <code>0</code> will be the <code>cursor
595+
* cursor</code> for the next iteration of results. <code>"0"</code> will be the <code>cursor
596596
* </code> returned on the last iteration of the set. The second element is always an <code>
597597
* Array</code> of the subset of the set held in <code>key</code>.
598598
* @example

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1584,10 +1584,10 @@ CompletableFuture<Map<String, Double>> zinterWithScores(
15841584
*
15851585
* @see <a href="https://valkey.io/commands/zscan">valkey.io</a> for details.
15861586
* @param key The key of the sorted set.
1587-
* @param cursor The cursor that points to the next iteration of results. A value of 0 indicates
1588-
* the start of the search.
1587+
* @param cursor The cursor that points to the next iteration of results. A value of <code>"0"
1588+
* </code> indicates the start of the search.
15891589
* @return An <code>Array</code> of <code>Objects</code>. The first element is always the <code>
1590-
* cursor</code> for the next iteration of results. <code>0</code> will be the <code>cursor
1590+
* cursor</code> for the next iteration of results. <code>"0"</code> will be the <code>cursor
15911591
* </code> returned on the last iteration of the sorted set. The second element is always an
15921592
* <code>
15931593
* Array</code> of the subset of the sorted set held in <code>key</code>. The array in the
@@ -1620,11 +1620,11 @@ CompletableFuture<Map<String, Double>> zinterWithScores(
16201620
*
16211621
* @see <a href="https://valkey.io/commands/zscan">valkey.io</a> for details.
16221622
* @param key The key of the sorted set.
1623-
* @param cursor The cursor that points to the next iteration of results. A value of 0 indicates
1624-
* the start of the search.
1623+
* @param cursor The cursor that points to the next iteration of results. A value of <code>"0"
1624+
* </code> indicates the start of the search.
16251625
* @param zScanOptions The {@link ZScanOptions}.
16261626
* @return An <code>Array</code> of <code>Objects</code>. The first element is always the <code>
1627-
* cursor</code> for the next iteration of results. <code>0</code> will be the <code>cursor
1627+
* cursor</code> for the next iteration of results. <code>"0"</code> will be the <code>cursor
16281628
* </code> returned on the last iteration of the sorted set. The second element is always an
16291629
* <code>
16301630
* Array</code> of the subset of the sorted set held in <code>key</code>. The array in the

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

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
import static redis_request.RedisRequestOuterClass.RequestType.HLen;
8585
import static redis_request.RedisRequestOuterClass.RequestType.HMGet;
8686
import static redis_request.RedisRequestOuterClass.RequestType.HRandField;
87+
import static redis_request.RedisRequestOuterClass.RequestType.HScan;
8788
import static redis_request.RedisRequestOuterClass.RequestType.HSet;
8889
import static redis_request.RedisRequestOuterClass.RequestType.HSetNX;
8990
import static redis_request.RedisRequestOuterClass.RequestType.HStrlen;
@@ -249,6 +250,7 @@
249250
import glide.api.models.commands.geospatial.GeoSearchStoreOptions;
250251
import glide.api.models.commands.geospatial.GeoUnit;
251252
import glide.api.models.commands.geospatial.GeospatialData;
253+
import glide.api.models.commands.scan.HScanOptions;
252254
import glide.api.models.commands.scan.SScanOptions;
253255
import glide.api.models.commands.scan.ZScanOptions;
254256
import glide.api.models.commands.stream.StreamAddOptions;
@@ -5509,10 +5511,10 @@ public T geosearchstore(
55095511
*
55105512
* @see <a href="https://valkey.io/commands/sscan">valkey.io</a> for details.
55115513
* @param key The key of the set.
5512-
* @param cursor The cursor that points to the next iteration of results. A value of 0 indicates
5513-
* the start of the search.
5514+
* @param cursor The cursor that points to the next iteration of results. A value of <code>"0"
5515+
* </code> indicates the start of the search.
55145516
* @return Command Response - An <code>Array</code> of <code>Objects</code>. The first element is
5515-
* always the <code>cursor</code> for the next iteration of results. <code>0</code> will be
5517+
* always the <code>cursor</code> for the next iteration of results. <code>"0"</code> will be
55165518
* the <code>cursor</code> returned on the last iteration of the set. The second element is
55175519
* always an <code>Array</code> of the subset of the set held in <code>key</code>.
55185520
*/
@@ -5526,11 +5528,11 @@ public T sscan(@NonNull String key, @NonNull String cursor) {
55265528
*
55275529
* @see <a href="https://valkey.io/commands/sscan">valkey.io</a> for details.
55285530
* @param key The key of the set.
5529-
* @param cursor The cursor that points to the next iteration of results. A value of 0 indicates
5530-
* the start of the search.
5531+
* @param cursor The cursor that points to the next iteration of results. A value of <code>"0"
5532+
* </code> indicates the start of the search.
55315533
* @param sScanOptions The {@link SScanOptions}.
55325534
* @return Command Response - An <code>Array</code> of <code>Objects</code>. The first element is
5533-
* always the <code>cursor</code> for the next iteration of results. <code>0</code> will be
5535+
* always the <code>cursor</code> for the next iteration of results. <code>"0"</code> will be
55345536
* the <code>cursor</code> returned on the last iteration of the set. The second element is
55355537
* always an <code>Array</code> of the subset of the set held in <code>key</code>.
55365538
*/
@@ -5546,10 +5548,10 @@ public T sscan(@NonNull String key, @NonNull String cursor, @NonNull SScanOption
55465548
*
55475549
* @see <a href="https://valkey.io/commands/zscan">valkey.io</a> for details.
55485550
* @param key The key of the sorted set.
5549-
* @param cursor The cursor that points to the next iteration of results. A value of 0 indicates
5550-
* the start of the search.
5551+
* @param cursor The cursor that points to the next iteration of results. A value of <code>"0"
5552+
* </code> indicates the start of the search.
55515553
* @return Command Response - An <code>Array</code> of <code>Objects</code>. The first element is
5552-
* always the <code>cursor</code> for the next iteration of results. <code>0</code> will be
5554+
* always the <code>cursor</code> for the next iteration of results. <code>"0"</code> will be
55535555
* the <code>cursor</code> returned on the last iteration of the sorted set. The second
55545556
* element is always an <code>Array</code> of the subset of the sorted set held in <code>key
55555557
* </code>. The array in the second element is always a flattened series of <code>String
@@ -5565,11 +5567,11 @@ public T zscan(@NonNull String key, @NonNull String cursor) {
55655567
*
55665568
* @see <a href="https://valkey.io/commands/zscan">valkey.io</a> for details.
55675569
* @param key The key of the sorted set.
5568-
* @param cursor The cursor that points to the next iteration of results. A value of 0 indicates
5569-
* the start of the search.
5570+
* @param cursor The cursor that points to the next iteration of results. A value of <code>"0"
5571+
* </code> indicates the start of the search.
55705572
* @param zScanOptions The {@link ZScanOptions}.
55715573
* @return Command Response - An <code>Array</code> of <code>Objects</code>. The first element is
5572-
* always the <code>cursor</code> for the next iteration of results. <code>0</code> will be
5574+
* always the <code>cursor</code> for the next iteration of results. <code>"0"</code> will be
55735575
* the <code>cursor</code> returned on the last iteration of the sorted set. The second
55745576
* element is always an <code>Array</code> of the subset of the sorted set held in <code>key
55755577
* </code>. The array in the second element is always a flattened series of <code>String
@@ -5582,6 +5584,47 @@ public T zscan(@NonNull String key, @NonNull String cursor, @NonNull ZScanOption
55825584
return getThis();
55835585
}
55845586

5587+
/**
5588+
* Iterates fields of Hash types and their associated values.
5589+
*
5590+
* @see <a href="https://valkey.io/commands/hscan">valkey.io</a> for details.
5591+
* @param key The key of the hash.
5592+
* @param cursor The cursor that points to the next iteration of results. A value of <code>"0"
5593+
* </code> indicates the start of the search.
5594+
* @return Command Response - An <code>Array</code> of <code>Objects</code>. The first element is
5595+
* always the <code>cursor</code> for the next iteration of results. <code>"0"</code> will be
5596+
* the <code>cursor</code> returned on the last iteration of the result. The second element is
5597+
* always an <code>Array</code> of the subset of the hash held in <code>key</code>. The array
5598+
* in the second element is always a flattened series of <code>String</code> pairs, where the
5599+
* key is at even indices and the value is at odd indices.
5600+
*/
5601+
public T hscan(@NonNull String key, @NonNull String cursor) {
5602+
protobufTransaction.addCommands(buildCommand(HScan, buildArgs(key, cursor)));
5603+
return getThis();
5604+
}
5605+
5606+
/**
5607+
* Iterates fields of Hash types and their associated values.
5608+
*
5609+
* @see <a href="https://valkey.io/commands/hscan">valkey.io</a> for details.
5610+
* @param key The key of the hash.
5611+
* @param cursor The cursor that points to the next iteration of results. A value of <code>"0"
5612+
* </code> indicates the start of the search.
5613+
* @param hScanOptions The {@link HScanOptions}.
5614+
* @return Command Response - An <code>Array</code> of <code>Objects</code>. The first element is
5615+
* always the <code>cursor</code> for the next iteration of results. <code>"0"</code> will be
5616+
* the <code>cursor</code> returned on the last iteration of the result. The second element is
5617+
* always an <code>Array</code> of the subset of the hash held in <code>key</code>. The array
5618+
* in the second element is always a flattened series of <code>String</code> pairs, where the
5619+
* key is at even indices and the value is at odd indices.
5620+
*/
5621+
public T hscan(@NonNull String key, @NonNull String cursor, @NonNull HScanOptions hScanOptions) {
5622+
final ArgsArray commandArgs =
5623+
buildArgs(concatenateArrays(new String[] {key, cursor}, hScanOptions.toArgs()));
5624+
protobufTransaction.addCommands(buildCommand(HScan, commandArgs));
5625+
return getThis();
5626+
}
5627+
55855628
/** Build protobuf {@link Command} object for given command and arguments. */
55865629
protected Command buildCommand(RequestType requestType) {
55875630
return buildCommand(requestType, buildArgs());
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */
2+
package glide.api.models.commands.scan;
3+
4+
import glide.api.commands.HashBaseCommands;
5+
import lombok.experimental.SuperBuilder;
6+
7+
/**
8+
* Optional arguments for {@link HashBaseCommands#hscan(String, String, HScanOptions)}.
9+
*
10+
* @see <a href="https://valkey.io/commands/hscan/">valkey.io</a>
11+
*/
12+
@SuperBuilder
13+
public class HScanOptions extends BaseScanOptions {}

java/client/src/main/java/module-info.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
exports glide.api.models.commands.bitmap;
77
exports glide.api.models.commands.geospatial;
88
exports glide.api.models.commands.function;
9+
exports glide.api.models.commands.scan;
910
exports glide.api.models.commands.stream;
1011
exports glide.api.models.configuration;
1112
exports glide.api.models.exceptions;
12-
exports glide.api.models.commands.scan;
1313

1414
requires com.google.protobuf;
1515
requires io.netty.codec;

0 commit comments

Comments
 (0)