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 Zrandmember command. (Sorted Set Commands) #1238

Merged
Merged
Show file tree
Hide file tree
Changes from 4 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
18 changes: 17 additions & 1 deletion glide-core/src/client/value_conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,9 @@ pub(crate) fn expected_type_for_cmd(cmd: &Cmd) -> Option<ExpectedReturnType> {
b"HRANDFIELD" => cmd
.position(b"WITHVALUES")
.map(|_| ExpectedReturnType::ArrayOfKeyValuePairs),
b"ZRANDMEMBER" => cmd
.position(b"WITHSCORES")
.map(|_| ExpectedReturnType::ArrayOfKeyValuePairs),
b"ZADD" => cmd
.position(b"INCR")
.map(|_| ExpectedReturnType::DoubleOrNull),
Expand Down Expand Up @@ -514,7 +517,7 @@ mod tests {
}

#[test]
fn convert_hrandfield() {
fn convert_array_of_kv_pairs() {
assert!(matches!(
expected_type_for_cmd(
redis::cmd("HRANDFIELD")
Expand All @@ -528,6 +531,19 @@ mod tests {
assert!(expected_type_for_cmd(redis::cmd("HRANDFIELD").arg("key").arg("1")).is_none());
assert!(expected_type_for_cmd(redis::cmd("HRANDFIELD").arg("key")).is_none());

assert!(matches!(
expected_type_for_cmd(
redis::cmd("ZRANDMEMBER")
.arg("key")
.arg("1")
.arg("withscores")
),
Some(ExpectedReturnType::ArrayOfKeyValuePairs)
));

assert!(expected_type_for_cmd(redis::cmd("ZRANDMEMBER").arg("key").arg("1")).is_none());
assert!(expected_type_for_cmd(redis::cmd("ZRANDMEMBER").arg("key")).is_none());

let flat_array = Value::Array(vec![
Value::BulkString(b"key1".to_vec()),
Value::BulkString(b"value1".to_vec()),
Expand Down
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 @@ -179,6 +179,7 @@ enum RequestType {
ZUnion = 136;
BZPopMin = 137;
FlushAll = 138;
ZRandMember = 139;
}

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 @@ -147,6 +147,7 @@ pub enum RequestType {
ZUnion = 136,
BZPopMin = 137,
FlushAll = 138,
ZRandMember = 139,
}

fn get_two_word_command(first: &str, second: &str) -> Cmd {
Expand Down Expand Up @@ -297,6 +298,7 @@ impl From<::protobuf::EnumOrUnknown<ProtobufRequestType>> for RequestType {
ProtobufRequestType::ZUnion => RequestType::ZUnion,
ProtobufRequestType::BZPopMin => RequestType::BZPopMin,
ProtobufRequestType::FlushAll => RequestType::FlushAll,
ProtobufRequestType::ZRandMember => RequestType::ZRandMember,
}
}
}
Expand Down Expand Up @@ -443,6 +445,7 @@ impl RequestType {
RequestType::ZUnion => Some(cmd("ZUNION")),
RequestType::BZPopMin => Some(cmd("BZPOPMIN")),
RequestType::FlushAll => Some(cmd("FLUSHALL")),
RequestType::ZRandMember => Some(cmd("ZRANDMEMBER")),
}
}
}
25 changes: 25 additions & 0 deletions java/client/src/main/java/glide/api/BaseClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.ZMScore;
import static redis_request.RedisRequestOuterClass.RequestType.ZPopMax;
import static redis_request.RedisRequestOuterClass.RequestType.ZPopMin;
import static redis_request.RedisRequestOuterClass.RequestType.ZRandMember;
import static redis_request.RedisRequestOuterClass.RequestType.ZRangeStore;
import static redis_request.RedisRequestOuterClass.RequestType.ZRemRangeByLex;
import static redis_request.RedisRequestOuterClass.RequestType.ZRemRangeByRank;
Expand Down Expand Up @@ -1016,6 +1017,30 @@ public CompletableFuture<Map<String, Double>> zunionWithScores(
return commandManager.submitNewCommand(ZUnion, arguments, this::handleMapResponse);
}

@Override
public CompletableFuture<String> zrandmember(@NonNull String key) {
return commandManager.submitNewCommand(
ZRandMember, new String[] {key}, this::handleStringOrNullResponse);
}

@Override
public CompletableFuture<String[]> zrandmemberWithCount(@NonNull String key, long count) {
return commandManager.submitNewCommand(
ZRandMember,
new String[] {key, Long.toString(count)},
response -> castArray(handleArrayResponse(response), String.class));
}

@Override
public CompletableFuture<Object[][]> zrandmemberWithCountWithScores(
@NonNull String key, long count) {
String[] arguments = new String[] {key, Long.toString(count), WITH_SCORES_REDIS_API};
return commandManager.submitNewCommand(
ZRandMember,
arguments,
response -> castArray(handleArrayResponse(response), Object[].class));
}

@Override
public CompletableFuture<String> xadd(@NonNull String key, @NonNull Map<String, String> values) {
return xadd(key, values, StreamAddOptions.builder().build());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1014,4 +1014,67 @@ CompletableFuture<Map<String, Double>> zunionWithScores(
* }</pre>
*/
CompletableFuture<Map<String, Double>> zunionWithScores(KeysOrWeightedKeys keysOrWeightedKeys);

/**
* Returns a random element from the sorted set stored at <code>key</code>.
*
* @see <a href="https://redis.io/commands/zrandmember/">redis.io</a> for more details.
* @param key The key of the sorted set.
* @return A <code>String</code> representing a random element from the sorted set.<br>
* If the sorted set does not exist or is empty, the response will be <code>null</code>.
* @example
* <pre>{@code
* String payload1 = client.zrandmember("mySortedSet").get();
* assert payload1.equals("GLIDE");
*
* String payload2 = client.zrandmember("nonExistingSortedSet").get();
* assert payload2 == null;
* }</pre>
*/
CompletableFuture<String> zrandmember(String key);

/**
* Retrieves random elements from the sorted set stored at <code>key</code>.
*
* @see <a href="https://redis.io/commands/zrandmember/">redis.io</a> for more details.
* @param key The key of the sorted set.
* @param count The number of elements to return.<br>
* If <code>count</code> is positive, returns unique elements.<br>
* If negative, allows for duplicates.<br>
* @return An <code>array</code> of elements from the sorted set.<br>
* If the sorted set does not exist or is empty, the response will be an empty <code>array
* </code>.
* @example
* <pre>{@code
* String[] payload1 = client.zrandmember("mySortedSet", -3).get();
* assert payload1.equals(new String[] {"GLIDE", "GLIDE", "JAVA"});
*
* String[] payload2 = client.zrandmember("nonExistingSortedSet", 3).get();
* assert payload2.length == 0;
* }</pre>
*/
CompletableFuture<String[]> zrandmemberWithCount(String key, long count);

/**
* Retrieves random elements along with their scores from the sorted stored at <code>key</code>.
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
*
* @see <a href="https://redis.io/commands/zrandmember/">redis.io</a> for more details.
* @param key The key of the sorted set.
* @param count The number of elements to return.<br>
* If <code>count</code> is positive, returns unique elements.<br>
* If negative, allows duplicates.<br>
* @return An <code>array</code> of <code>[element, score]</code> <code>arrays</code>, where
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
* element is a <code>String</code> and score is a <code>Double</code>.<br>
* If the sorted set does not exist or is empty, the response will be an empty <code>array
* </code>.
* @example
* <pre>{@code
* Object[][] data = client.zrandmemberWithCountWithScores(key1, -3).get();
* assert data.length == 3;
* for (Object[] memberScorePair : data) {
* System.out.printf("Member: '%s', score: %d", memberScorePair[0], memberScorePair[1]);
* }
* }</pre>
*/
CompletableFuture<Object[][]> zrandmemberWithCountWithScores(String key, long count);
}
56 changes: 56 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 @@ -105,6 +105,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.ZMScore;
import static redis_request.RedisRequestOuterClass.RequestType.ZPopMax;
import static redis_request.RedisRequestOuterClass.RequestType.ZPopMin;
import static redis_request.RedisRequestOuterClass.RequestType.ZRandMember;
import static redis_request.RedisRequestOuterClass.RequestType.ZRangeStore;
import static redis_request.RedisRequestOuterClass.RequestType.ZRemRangeByLex;
import static redis_request.RedisRequestOuterClass.RequestType.ZRemRangeByRank;
Expand Down Expand Up @@ -1599,6 +1600,61 @@ public T zpopmin(@NonNull String key) {
return getThis();
}

/**
* Returns a random element from the sorted set stored at <code>key</code>.
*
* @see <a href="https://redis.io/commands/zrandmember/">redis.io</a> for more details.
* @param key The key of the sorted set.
* @return Command Response - A <code>String</code> representing a random element from the sorted
* set.<br>
* If the sorted set does not exist or is empty, the response will be <code>null</code>.
*/
public T zrandmember(@NonNull String key) {
ArgsArray commandArgs = buildArgs(key);
protobufTransaction.addCommands(buildCommand(ZRandMember, commandArgs));
return getThis();
}

/**
* Retrieves random elements from the sorted set stored at <code>key</code>.
*
* @see <a href="https://redis.io/commands/zrandmember/">redis.io</a> for more details.
* @param key The key of the sorted set.
* @param count The number of elements to return.<br>
* If <code>count</code> is positive, returns unique elements.<br>
* If negative, allows for duplicates.<br>
* @return Command Response - An <code>array</code> of elements from the sorted set.<br>
* If the sorted set does not exist or is empty, the response will be an empty <code>array
* </code>.
*/
public T zrandmemberWithCount(@NonNull String key, long count) {
ArgsArray commandArgs = buildArgs(key, Long.toString(count));
protobufTransaction.addCommands(buildCommand(ZRandMember, commandArgs));
return getThis();
}

/**
* Retrieves random elements along with their scores from the sorted set stored at <code>key
* </code>.
*
* @see <a href="https://redis.io/commands/zrandmember/">redis.io</a> for more details.
* @param key The key of the sorted set.
* @param count The number of elements to return.<br>
* If <code>count</code> is positive, returns unique elements.<br>
* If negative, allows duplicates.<br>
* @return Command Response - An <code>array</code> of <code>[element, score]</code> <code>arrays
* </code>, where element is a <code>String</code> and score is a <code>Double</code>.<br>
* If the sorted set does not exist or is empty, the response will be an empty <code>array
* </code>.
*/
public T zrandmemberWithCountWithScores(String key, long count) {
String[] arguments = new String[] {key, Long.toString(count), WITH_SCORES_REDIS_API};

ArgsArray commandArgs = buildArgs(arguments);
protobufTransaction.addCommands(buildCommand(ZRandMember, commandArgs));
return getThis();
}

/**
* Blocks the connection until it removes and returns a member with the lowest score from the
* sorted sets stored at the specified <code>keys</code>. The sorted sets are checked in the order
Expand Down
76 changes: 75 additions & 1 deletion java/client/src/test/java/glide/api/RedisClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.ZMScore;
import static redis_request.RedisRequestOuterClass.RequestType.ZPopMax;
import static redis_request.RedisRequestOuterClass.RequestType.ZPopMin;
import static redis_request.RedisRequestOuterClass.RequestType.ZRandMember;
import static redis_request.RedisRequestOuterClass.RequestType.ZRangeStore;
import static redis_request.RedisRequestOuterClass.RequestType.ZRemRangeByLex;
import static redis_request.RedisRequestOuterClass.RequestType.ZRemRangeByRank;
Expand Down Expand Up @@ -3193,11 +3194,84 @@ public void xadd_returns_success() {

// exercise
CompletableFuture<String> response = service.xadd(key, fieldValues);

// verify
assertEquals(testResponse, response);
assertEquals(returnId, response.get());
}

@SneakyThrows
@Test
public void zrandmember_returns_success() {
// setup
String key = "testKey";
String[] arguments = new String[] {key};
String value = "testValue";

CompletableFuture<String> testResponse = new CompletableFuture<>();
testResponse.complete(value);

// match on protobuf request
when(commandManager.<String>submitNewCommand(eq(ZRandMember), eq(arguments), any()))
.thenReturn(testResponse);

// exercise
CompletableFuture<String> response = service.zrandmember(key);
String payload = response.get();

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

@SneakyThrows
@Test
public void zrandmemberWithCount_returns_success() {
// setup
String key = "testKey";
long count = 2L;
String[] arguments = new String[] {key, Long.toString(count)};
String[] value = new String[] {"member1", "member2"};

CompletableFuture<String[]> testResponse = new CompletableFuture<>();
testResponse.complete(value);

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

// exercise
CompletableFuture<String[]> response = service.zrandmemberWithCount(key, count);
String[] payload = response.get();

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

@SneakyThrows
@Test
public void zrandmemberWithCountWithScores_returns_success() {
// setup
String key = "testKey";
long count = 2L;
String[] arguments = new String[] {key, Long.toString(count), WITH_SCORES_REDIS_API};
Object[][] value = new Object[][] {{"member1", 2.0}, {"member2", 3.0}};

CompletableFuture<Object[][]> testResponse = new CompletableFuture<>();
testResponse.complete(value);

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

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

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

private static List<Arguments> getStreamAddOptions() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.ZMScore;
import static redis_request.RedisRequestOuterClass.RequestType.ZPopMax;
import static redis_request.RedisRequestOuterClass.RequestType.ZPopMin;
import static redis_request.RedisRequestOuterClass.RequestType.ZRandMember;
import static redis_request.RedisRequestOuterClass.RequestType.ZRangeStore;
import static redis_request.RedisRequestOuterClass.RequestType.ZRemRangeByLex;
import static redis_request.RedisRequestOuterClass.RequestType.ZRemRangeByRank;
Expand Down Expand Up @@ -575,6 +576,22 @@ InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3, false), new Limit(1, 2)),
transaction.persist("key");
results.add(Pair.of(Persist, buildArgs("key")));

transaction.zrandmember("key");
results.add(Pair.of(ZRandMember, ArgsArray.newBuilder().addArgs("key").build()));

transaction.zrandmemberWithCount("key", 5);
results.add(Pair.of(ZRandMember, ArgsArray.newBuilder().addArgs("key").addArgs("5").build()));

transaction.zrandmemberWithCountWithScores("key", 5);
results.add(
Pair.of(
ZRandMember,
ArgsArray.newBuilder()
.addArgs("key")
.addArgs("5")
.addArgs(WITH_SCORES_REDIS_API)
.build()));

transaction.type("key");
results.add(Pair.of(Type, buildArgs("key")));

Expand Down
Loading
Loading