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 HSCAN command #401

Closed
wants to merge 4 commits into from
Closed
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 @@ -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
Expand Up @@ -11,7 +11,7 @@
* and ZSCAN).
*/
@SuperBuilder
public abstract class ScanOptions {
public abstract class BaseScanOptions {
/** <code>MATCH</code> option string to include in the <code>SCAN</code> commands. */
public static final String MATCH_OPTION_STRING = "MATCH";

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 {}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@
* @see <a href="https://valkey.io/commands/sscan/">valkey.io</a>
*/
@SuperBuilder
public class SScanOptions extends ScanOptions {}
public class SScanOptions extends BaseScanOptions {}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@
* @see <a href="https://valkey.io/commands/zscan/">valkey.io</a>
*/
@SuperBuilder
public class ZScanOptions extends ScanOptions {}
public class ZScanOptions extends BaseScanOptions {}
58 changes: 56 additions & 2 deletions java/client/src/test/java/glide/api/RedisClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@
import static glide.api.models.commands.geospatial.GeoAddOptions.CHANGED_REDIS_API;
import static glide.api.models.commands.geospatial.GeoSearchOrigin.FROMLONLAT_VALKEY_API;
import static glide.api.models.commands.geospatial.GeoSearchOrigin.FROMMEMBER_VALKEY_API;
import static glide.api.models.commands.scan.ScanOptions.COUNT_OPTION_STRING;
import static glide.api.models.commands.scan.ScanOptions.MATCH_OPTION_STRING;
import static glide.api.models.commands.scan.BaseScanOptions.COUNT_OPTION_STRING;
import static glide.api.models.commands.scan.BaseScanOptions.MATCH_OPTION_STRING;
import static glide.api.models.commands.stream.StreamAddOptions.NO_MAKE_STREAM_REDIS_API;
import static glide.api.models.commands.stream.StreamGroupOptions.ENTRIES_READ_REDIS_API;
import static glide.api.models.commands.stream.StreamGroupOptions.MAKE_STREAM_REDIS_API;
Expand Down 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
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,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 @@ -260,6 +261,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 @@ -1352,6 +1354,12 @@ InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3, false), new Limit(1, 2)),
transaction.zscan("key1", "0", ZScanOptions.builder().matchPattern("*").count(10L).build());
results.add(Pair.of(ZScan, buildArgs("key1", "0", "MATCH", "*", "COUNT", "10")));

transaction.hscan("key1", "0");
results.add(Pair.of(HScan, buildArgs("key1", "0")));

transaction.hscan("key1", "0", HScanOptions.builder().matchPattern("*").count(10L).build());
results.add(Pair.of(HScan, buildArgs("key1", "0", "MATCH", "*", "COUNT", "10")));

var protobufTransaction = transaction.getProtobufTransaction().build();

for (int idx = 0; idx < protobufTransaction.getCommandsCount(); idx++) {
Expand Down
Loading
Loading