Skip to content
Merged
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 @@ -168,6 +168,7 @@ enum RequestType {
LOLWUT = 100500;
GeoDist = 127;
GeoPos = 128;
BZPopMax = 129;
}

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 @@ -136,6 +136,7 @@ pub enum RequestType {
LOLWUT = 100500,
GeoDist = 127,
GeoPos = 128,
BZPopMax = 129,
}

fn get_two_word_command(first: &str, second: &str) -> Cmd {
Expand Down Expand Up @@ -275,6 +276,7 @@ impl From<::protobuf::EnumOrUnknown<ProtobufRequestType>> for RequestType {
ProtobufRequestType::ObjectRefcount => RequestType::ObjectRefcount,
ProtobufRequestType::LOLWUT => RequestType::LOLWUT,
ProtobufRequestType::GeoPos => RequestType::GeoPos,
ProtobufRequestType::BZPopMax => RequestType::BZPopMax,
}
}
}
Expand Down Expand Up @@ -410,6 +412,7 @@ impl RequestType {
RequestType::ObjectRefcount => Some(get_two_word_command("OBJECT", "REFCOUNT")),
RequestType::LOLWUT => Some(cmd("LOLWUT")),
RequestType::GeoPos => Some(cmd("GEOPOS")),
RequestType::BZPopMax => Some(cmd("BZPOPMAX")),
}
}
}
7 changes: 7 additions & 0 deletions java/client/src/main/java/glide/api/BaseClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import static glide.utils.ArrayTransformUtils.concatenateArrays;
import static glide.utils.ArrayTransformUtils.convertMapToKeyValueStringArray;
import static glide.utils.ArrayTransformUtils.convertMapToValueKeyStringArray;
import static redis_request.RedisRequestOuterClass.RequestType.BZPopMax;
import static redis_request.RedisRequestOuterClass.RequestType.Blpop;
import static redis_request.RedisRequestOuterClass.RequestType.Brpop;
import static redis_request.RedisRequestOuterClass.RequestType.Decr;
Expand Down Expand Up @@ -786,6 +787,12 @@ public CompletableFuture<Map<String, Double>> zpopmax(@NonNull String key) {
return commandManager.submitNewCommand(ZPopMax, new String[] {key}, this::handleMapResponse);
}

@Override
public CompletableFuture<Object[]> bzpopmax(@NonNull String[] keys, double timeout) {
String[] arguments = ArrayUtils.add(keys, Double.toString(timeout));
return commandManager.submitNewCommand(BZPopMax, arguments, this::handleArrayOrNullResponse);
}

@Override
public CompletableFuture<Double> zscore(@NonNull String key, @NonNull String member) {
return commandManager.submitNewCommand(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,31 @@ CompletableFuture<Double> zaddIncr(
*/
CompletableFuture<Map<String, Double>> zpopmax(String key);

/**
* Blocks the connection until it removes and returns a member with the highest score from the
* sorted sets stored at the specified <code>keys</code>. The sorted sets are checked in the order
* they are provided.<br>
* <code>BZPOPMAX</code> is the blocking variant of {@link #zpopmax(String)}.<br>
*
* @see <a href="https://redis.io/commands/bzpopmax/">redis.io</a> for more details.
* @apiNote <code>BZPOPMAX</code> is a client blocking command, see <a
* href="https://github.com/aws/glide-for-redis/wiki/General-Concepts#blocking-commands">Blocking
* Commands</a> for more details and best practices.
* @param keys The keys of the sorted sets.
* @param timeout The number of seconds to wait for a blocking operation to complete. A value of
* <code>0</code> will block indefinitely.
* @return An <code>array</code> containing the key where the member was popped out, the member
* itself, and the member score.<br>
* If no member could be popped and the <code>timeout</code> expired, returns </code>null
* </code>.
* @example
* <pre>{@code
* Object[] data = client.bzpopmax(new String[] {"zset1", "zset2"}, 0.5).get();
* System.out.printf("Popped '%s' with score %d from sorted set '%s'%n", data[1], data[2], data[0]);
* }</pre>
*/
CompletableFuture<Object[]> bzpopmax(String[] keys, double timeout);

/**
* Returns the score of <code>member</code> in the sorted set stored at <code>key</code>.
*
Expand Down
25 changes: 25 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 @@ -8,6 +8,7 @@
import static glide.utils.ArrayTransformUtils.concatenateArrays;
import static glide.utils.ArrayTransformUtils.convertMapToKeyValueStringArray;
import static glide.utils.ArrayTransformUtils.convertMapToValueKeyStringArray;
import static redis_request.RedisRequestOuterClass.RequestType.BZPopMax;
import static redis_request.RedisRequestOuterClass.RequestType.Blpop;
import static redis_request.RedisRequestOuterClass.RequestType.Brpop;
import static redis_request.RedisRequestOuterClass.RequestType.ClientGetName;
Expand Down Expand Up @@ -1600,6 +1601,30 @@ public T zpopmax(@NonNull String key) {
return getThis();
}

/**
* Blocks the connection until it removes and returns a member with the highest score from the
* sorted sets stored at the specified <code>keys</code>. The sorted sets are checked in the order
* they are provided.<br>
* <code>BZPOPMAX</code> is the blocking variant of {@link #zpopmax(String)}.<br>
*
* @see <a href="https://redis.io/commands/bzpopmax/">redis.io</a> for more details.
* @apiNote <code>BZPOPMAX</code> is a client blocking command, see <a
* href="https://github.com/aws/glide-for-redis/wiki/General-Concepts#blocking-commands">Blocking
* Commands</a> for more details and best practices.
* @param keys The keys of the sorted sets.
* @param timeout The number of seconds to wait for a blocking operation to complete. A value of
* <code>0</code> will block indefinitely.
* @return Command Response - An <code>array</code> containing the key where the member was popped
* out, the member itself, and the member score.<br>
* If no member could be popped and the <code>timeout</code> expired, returns </code>null
* </code>.
*/
public T bzpopmax(@NonNull String[] keys, double timeout) {
ArgsArray commandArgs = buildArgs(ArrayUtils.add(keys, Double.toString(timeout)));
protobufTransaction.addCommands(buildCommand(BZPopMax, commandArgs));
return getThis();
}

/**
* Returns the score of <code>member</code> in the sorted set stored at <code>key</code>.
*
Expand Down
26 changes: 26 additions & 0 deletions java/client/src/test/java/glide/api/RedisClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static redis_request.RedisRequestOuterClass.RequestType.BZPopMax;
import static redis_request.RedisRequestOuterClass.RequestType.Blpop;
import static redis_request.RedisRequestOuterClass.RequestType.Brpop;
import static redis_request.RedisRequestOuterClass.RequestType.ClientGetName;
Expand Down Expand Up @@ -2299,6 +2300,31 @@ public void zpopmax_returns_success() {
assertEquals(value, payload);
}

@SneakyThrows
@Test
public void bzpopmax_returns_success() {
// setup
String[] keys = new String[] {"key1", "key2"};
double timeout = .5;
String[] arguments = new String[] {"key1", "key2", "0.5"};
Object[] value = new Object[] {"key1", "elem", 42.};

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

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

// exercise
CompletableFuture<Object[]> response = service.bzpopmax(keys, timeout);
Object[] payload = response.get();

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

@SneakyThrows
@Test
public void zpopmax_with_count_returns_success() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import static glide.api.models.commands.SetOptions.RETURN_OLD_VALUE;
import static glide.api.models.commands.ZaddOptions.UpdateOptions.SCORE_LESS_THAN_CURRENT;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static redis_request.RedisRequestOuterClass.RequestType.BZPopMax;
import static redis_request.RedisRequestOuterClass.RequestType.Blpop;
import static redis_request.RedisRequestOuterClass.RequestType.Brpop;
import static redis_request.RedisRequestOuterClass.RequestType.ClientGetName;
Expand Down Expand Up @@ -385,6 +386,9 @@ public void transaction_builds_protobuf_request(BaseTransaction<?> transaction)
transaction.zpopmax("key");
results.add(Pair.of(ZPopMax, buildArgs("key")));

transaction.bzpopmax(new String[] {"key1", "key2"}, .5);
results.add(Pair.of(BZPopMax, buildArgs("key1", "key2", "0.5")));

transaction.zpopmax("key", 2);
results.add(Pair.of(ZPopMax, buildArgs("key", "2")));

Expand Down
31 changes: 31 additions & 0 deletions java/integTest/src/test/java/glide/SharedCommandTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -1651,6 +1651,37 @@ public void zpopmax(BaseClient client) {
assertTrue(executionException.getCause() instanceof RequestException);
}

@SneakyThrows
@ParameterizedTest(autoCloseArguments = false)
@MethodSource("getClients")
public void bzpopmax(BaseClient client) {
String key1 = "{test}-1-" + UUID.randomUUID();
String key2 = "{test}-2-" + UUID.randomUUID();
String key3 = "{test}-3-" + UUID.randomUUID();

assertEquals(2, client.zadd(key1, Map.of("a", 1.0, "b", 1.5)).get());
assertEquals(1, client.zadd(key2, Map.of("c", 2.0)).get());
assertArrayEquals(
new Object[] {key1, "b", 1.5}, client.bzpopmax(new String[] {key1, key2}, .5).get());

// nothing popped out - key does not exist
assertNull(
client
.bzpopmax(new String[] {key3}, REDIS_VERSION.isLowerThan("7.0.0") ? 1. : 0.001)
.get());

// pops from the second key
assertArrayEquals(
new Object[] {key2, "c", 2.0}, client.bzpopmax(new String[] {key3, key2}, .5).get());

// Key exists, but it is not a sorted set
assertEquals(OK, client.set(key3, "value").get());
ExecutionException executionException =
assertThrows(
ExecutionException.class, () -> client.bzpopmax(new String[] {key3}, .5).get());
assertTrue(executionException.getCause() instanceof RequestException);
}

@SneakyThrows
@ParameterizedTest(autoCloseArguments = false)
@MethodSource("getClients")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ public static BaseTransaction<?> transactionTest(BaseTransaction<?> baseTransact
baseTransaction.zadd(zSetKey2, Map.of("one", 1.0, "two", 2.0));
baseTransaction.zdiff(new String[] {zSetKey2, key8});
baseTransaction.zdiffWithScores(new String[] {zSetKey2, key8});
baseTransaction.bzpopmax(new String[] {zSetKey2}, .1);

baseTransaction.xadd(
key9, Map.of("field1", "value1"), StreamAddOptions.builder().id("0-1").build());
Expand Down Expand Up @@ -253,6 +254,7 @@ public static Object[] transactionTestResult() {
2L, // zadd(zSetKey2, Map.of("one", 1.0, "two", 2.0))
new String[] {"one", "two"}, // zdiff(new String[] {zSetKey2, key8})
Map.of("one", 1.0, "two", 2.0), // zdiffWithScores(new String[] {zSetKey2, key8})
new Object[] {zSetKey2, "two", 2.0}, // bzpopmax(new String[] { zsetKey2 }, .1)
"0-1", // xadd(key9, Map.of("field1", "value1"),
// StreamAddOptions.builder().id("0-1").build());
"0-2", // xadd(key9, Map.of("field2", "value2"),
Expand Down