Skip to content

Commit

Permalink
HScan
Browse files Browse the repository at this point in the history
  • Loading branch information
jduo committed Jun 29, 2024
1 parent 6098957 commit fe1493a
Show file tree
Hide file tree
Showing 9 changed files with 372 additions and 1 deletion.
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);
}
}
70 changes: 70 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,73 @@ 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.
* @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 String 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.
* @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 String 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);
}
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 @@ -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 @@ -5578,6 +5580,45 @@ 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.
* @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 String 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.
* @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 String 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 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 @@ -138,6 +138,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 @@ -309,6 +310,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 @@ -9079,6 +9081,58 @@ public void zscan_with_options_returns_success() {
assertEquals(value, payload);
}

@SneakyThrows
@Test
public void hscan_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(HScan), eq(arguments), any()))
.thenReturn(testResponse);

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

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

@SneakyThrows
@Test
public void hscan_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(HScan), eq(arguments), any()))
.thenReturn(testResponse);

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

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

private static List<Arguments> getGeoSearchArguments() {
return List.of(
Arguments.of(
Expand Down
Loading

0 comments on commit fe1493a

Please sign in to comment.