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 command BLMPOP #1464

Merged
merged 2 commits into from
May 27, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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/client/value_conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,7 @@ pub(crate) fn expected_type_for_cmd(cmd: &Cmd) -> Option<ExpectedReturnType> {
b"JSON.TOGGLE" => Some(ExpectedReturnType::JsonToggleReturnType),
b"GEOPOS" => Some(ExpectedReturnType::ArrayOfArraysOfDoubleOrNull),
b"LMPOP" => Some(ExpectedReturnType::ArrayOfStringAndArrays),
b"BLMPOP" => Some(ExpectedReturnType::ArrayOfStringAndArrays),
b"HRANDFIELD" => cmd
.position(b"WITHVALUES")
.map(|_| ExpectedReturnType::ArrayOfPairs),
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 @@ -193,6 +193,7 @@ enum RequestType {
LMPop = 155;
ExpireTime = 156;
PExpireTime = 157;
BLMPop = 158;
}

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 @@ -163,6 +163,7 @@ pub enum RequestType {
LMPop = 155,
ExpireTime = 156,
PExpireTime = 157,
BLMPop = 158,
}

fn get_two_word_command(first: &str, second: &str) -> Cmd {
Expand Down Expand Up @@ -319,6 +320,7 @@ impl From<::protobuf::EnumOrUnknown<ProtobufRequestType>> for RequestType {
ProtobufRequestType::BZMPop => RequestType::BZMPop,
ProtobufRequestType::SetBit => RequestType::SetBit,
ProtobufRequestType::LMPop => RequestType::LMPop,
ProtobufRequestType::BLMPop => RequestType::BLMPop,
ProtobufRequestType::ZInterCard => RequestType::ZInterCard,
ProtobufRequestType::ZMPop => RequestType::ZMPop,
ProtobufRequestType::GetBit => RequestType::GetBit,
Expand Down Expand Up @@ -480,6 +482,7 @@ impl RequestType {
RequestType::BitCount => Some(cmd("BITCOUNT")),
RequestType::BZMPop => Some(cmd("BZMPOP")),
RequestType::LMPop => Some(cmd("LMPOP")),
RequestType::BLMPop => Some(cmd("BLMPOP")),
RequestType::SetBit => Some(cmd("SETBIT")),
RequestType::ZInterCard => Some(cmd("ZINTERCARD")),
RequestType::ZMPop => Some(cmd("ZMPOP")),
Expand Down
29 changes: 29 additions & 0 deletions java/client/src/main/java/glide/api/BaseClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import static glide.utils.ArrayTransformUtils.convertMapToValueKeyStringArray;
import static glide.utils.ArrayTransformUtils.mapGeoDataToArray;
import static redis_request.RedisRequestOuterClass.RequestType.Append;
import static redis_request.RedisRequestOuterClass.RequestType.BLMPop;
import static redis_request.RedisRequestOuterClass.RequestType.BLPop;
import static redis_request.RedisRequestOuterClass.RequestType.BRPop;
import static redis_request.RedisRequestOuterClass.RequestType.BZMPop;
Expand Down Expand Up @@ -1514,4 +1515,32 @@ public CompletableFuture<Map<String, String[]>> lmpop(
arguments,
response -> castMapOfArrays(handleMapOrNullResponse(response), String.class));
}

@Override
public CompletableFuture<Map<String, String[]>> blmpop(
@NonNull String[] keys, @NonNull PopDirection direction, long count, double timeout) {
String[] arguments =
concatenateArrays(
new String[] {Double.toString(timeout), Long.toString(keys.length)},
keys,
new String[] {direction.toString(), COUNT_FOR_LIST_REDIS_API, Long.toString(count)});
return commandManager.submitNewCommand(
BLMPop,
arguments,
response -> castMapOfArrays(handleMapOrNullResponse(response), String.class));
}

@Override
public CompletableFuture<Map<String, String[]>> blmpop(
@NonNull String[] keys, @NonNull PopDirection direction, double timeout) {
String[] arguments =
concatenateArrays(
new String[] {Double.toString(timeout), Long.toString(keys.length)},
keys,
new String[] {direction.toString()});
return commandManager.submitNewCommand(
BLMPop,
arguments,
response -> castMapOfArrays(handleMapOrNullResponse(response), String.class));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -415,4 +415,71 @@ CompletableFuture<Long> linsert(
* }</pre>
*/
CompletableFuture<Map<String, String[]>> lmpop(String[] keys, PopDirection direction);

/**
* Blocks the connection until it pops one or more elements from the first non-empty list from the
* provided <code>keys</code> <code>BLMPOP</code> is the blocking variant of <code>LMPOP
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
* </code>.
*
* @apiNote
* <ol>
* <li>When in cluster mode, all <code>keys</code> must map to the same hash slot.
* <li><code>BLMPOP</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.
* </ol>
*
* @since Redis 7.0 and above.
* @see <a href="https://valkey.io/commands/blmpop/">valkey.io</a> for details.
* @param keys An array of keys to lists.
* @param direction The direction based on which elements are popped from - see {@link
* PopDirection}.
* @param count The maximum number of popped elements.
* @param timeout The number of seconds to wait for a blocking operation to complete. A value of
* <code>0</code> will block indefinitely.
* @return A <code>Map</code> of <code>key</code> name mapped array of popped elements. <br>
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
* If no member could be popped and the timeout expired, returns <code>null</code>.
* @example
* <pre>{@code
* client.lpush("testKey", new String[] {"one", "two", "three"}).get();
* Map<String, String[]> result = client.blmpop(new String[] {"testKey"}, PopDirection.LEFT, 1L, 0.1).get();
* String[] resultValue = result.get("testKey");
* assertArrayEquals(new String[] {"three"}, resultValue);
* }</pre>
*/
CompletableFuture<Map<String, String[]>> blmpop(
String[] keys, PopDirection direction, long count, double timeout);

/**
* Blocks the connection until it pop one element from the first non-empty list from the provided
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
* <code>keys</code> <code>BLMPOP</code> is the blocking variant of <code>LMPOP
* </code>.
*
* @apiNote
* <ol>
* <li>When in cluster mode, all <code>keys</code> must map to the same hash slot.
* <li><code>BLMPOP</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.
* </ol>
*
* @since Redis 7.0 and above.
* @see <a href="https://valkey.io/commands/lmpop/">valkey.io</a> for details.
* @param keys An array of keys to lists.
* @param direction The direction based on which elements are popped from - see {@link
* PopDirection}.
* @param timeout The number of seconds to wait for a blocking operation to complete. A value of
* <code>0</code> will block indefinitely.
* @return A <code>Map</code> of <code>key</code> name mapped array of the popped element. <br>
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
* If no member could be popped and the timeout expired, returns <code>null</code>.
* @example
* <pre>{@code
* client.lpush("testKey", new String[] {"one", "two", "three"}).get();
* Map<String, String[]> result = client.blmpop(new String[] {"testKey"}, PopDirection.LEFT, 0.1).get();
* String[] resultValue = result.get("testKey");
* assertArrayEquals(new String[] {"three"}, resultValue);
* }</pre>
*/
CompletableFuture<Map<String, String[]>> blmpop(
String[] keys, PopDirection direction, double timeout);
}
66 changes: 66 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 @@ -14,6 +14,7 @@
import static glide.utils.ArrayTransformUtils.convertMapToValueKeyStringArray;
import static glide.utils.ArrayTransformUtils.mapGeoDataToArray;
import static redis_request.RedisRequestOuterClass.RequestType.Append;
import static redis_request.RedisRequestOuterClass.RequestType.BLMPop;
import static redis_request.RedisRequestOuterClass.RequestType.BLPop;
import static redis_request.RedisRequestOuterClass.RequestType.BRPop;
import static redis_request.RedisRequestOuterClass.RequestType.BZMPop;
Expand Down Expand Up @@ -3490,6 +3491,71 @@ public T getbit(@NonNull String key, long offset) {
return getThis();
}

/**
* Blocks the connection until it pops one or more elements from the first non-empty list from the
* provided <code>keys</code>. <code>BLMPOP</code> is the blocking variant of <code>LMPOP</code>.
*
* @apiNote <code>BLMPOP</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.
* @since Redis 7.0 and above.
* @see <a href="https://valkey.io/commands/blmpop/">valkey.io</a> for details.
* @param keys The list of provided <code>key</code> names.
* @param direction The direction based on which elements are popped from - see {@link
* PopDirection}.
* @param count The maximum number of popped elements.
* @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 - A <code>Map</code> of <code>key</code> names arrays of popped
* elements. <br>
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
* If no member could be popped and the timeout expired, returns <code>null</code>.
*/
public T blmpop(
@NonNull String[] keys,
@NonNull PopDirection direction,
@NonNull Long count,
double timeout) {
ArgsArray commandArgs =
buildArgs(
concatenateArrays(
new String[] {Double.toString(timeout), Long.toString(keys.length)},
keys,
new String[] {
direction.toString(), COUNT_FOR_LIST_REDIS_API, Long.toString(count)
}));
protobufTransaction.addCommands(buildCommand(BLMPop, commandArgs));
return getThis();
}

/**
* Blocks the connection until it pops one element from the first non-empty list from the provided
* <code>keys</code>. <code>BLMPOP</code> is the blocking variant of <code>LMPOP</code>.
*
* @apiNote <code>BLMPOP</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.
* @since Redis 7.0 and above.
* @see <a href="https://valkey.io/commands/lmpop/">valkey.io</a> for details.
* @param keys The list of provided <code>key</code> names.
* @param direction The direction based on which elements are popped from - see {@link
* PopDirection}.
* @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 - A <code>Map</code> of <code>key</code> names arrays of popped
* elements. <br>
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
* If no member could be popped and the timeout expired, returns <code>null</code>.
*/
public T blmpop(@NonNull String[] keys, @NonNull PopDirection direction, double timeout) {
ArgsArray commandArgs =
buildArgs(
concatenateArrays(
new String[] {Double.toString(timeout), Long.toString(keys.length)},
keys,
new String[] {direction.toString()}));
protobufTransaction.addCommands(buildCommand(BLMPop, commandArgs));
return getThis();
}

/**
* Returns the position of the first bit matching the given <code>bit</code> value.
*
Expand Down
69 changes: 69 additions & 0 deletions java/client/src/test/java/glide/api/RedisClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static redis_request.RedisRequestOuterClass.RequestType.Append;
import static redis_request.RedisRequestOuterClass.RequestType.BLMPop;
import static redis_request.RedisRequestOuterClass.RequestType.BLPop;
import static redis_request.RedisRequestOuterClass.RequestType.BRPop;
import static redis_request.RedisRequestOuterClass.RequestType.BZMPop;
Expand Down Expand Up @@ -4819,6 +4820,74 @@ public void setbit_returns_success() {
assertEquals(value, payload);
}

@SneakyThrows
@Test
public void blmpop_returns_success() {
// setup
String key = "testKey";
String key2 = "testKey2";
String[] keys = {key, key2};
PopDirection popDirection = PopDirection.LEFT;
double timeout = 0.1;
String[] arguments =
new String[] {Double.toString(timeout), "2", key, key2, popDirection.toString()};
Map<String, String[]> value = Map.of(key, new String[] {"five"});

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

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

// exercise
CompletableFuture<Map<String, String[]>> response = service.blmpop(keys, popDirection, timeout);
Map<String, String[]> payload = response.get();

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

@SneakyThrows
@Test
public void blmpop_with_count_returns_success() {
// setup
String key = "testKey";
String key2 = "testKey2";
String[] keys = {key, key2};
PopDirection popDirection = PopDirection.LEFT;
long count = 1L;
double timeout = 0.1;
String[] arguments =
new String[] {
Double.toString(timeout),
"2",
key,
key2,
popDirection.toString(),
COUNT_FOR_LIST_REDIS_API,
Long.toString(count)
};
Map<String, String[]> value = Map.of(key, new String[] {"five"});

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

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

// exercise
CompletableFuture<Map<String, String[]>> response =
service.blmpop(keys, popDirection, count, timeout);
Map<String, String[]> payload = response.get();

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

@SneakyThrows
@Test
public void getbit_returns_success() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import static glide.api.models.commands.stream.StreamTrimOptions.TRIM_MINID_REDIS_API;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static redis_request.RedisRequestOuterClass.RequestType.Append;
import static redis_request.RedisRequestOuterClass.RequestType.BLMPop;
import static redis_request.RedisRequestOuterClass.RequestType.BLPop;
import static redis_request.RedisRequestOuterClass.RequestType.BRPop;
import static redis_request.RedisRequestOuterClass.RequestType.BZMPop;
Expand Down Expand Up @@ -847,6 +848,11 @@ InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3, false), new Limit(1, 2)),
transaction.lmpop(new String[] {"key"}, PopDirection.LEFT, 1L);
results.add(Pair.of(LMPop, buildArgs("1", "key", "LEFT", "COUNT", "1")));

transaction.blmpop(new String[] {"key"}, PopDirection.LEFT, 0.1);
results.add(Pair.of(BLMPop, buildArgs("0.1", "1", "key", "LEFT")));
transaction.blmpop(new String[] {"key"}, PopDirection.LEFT, 1L, 0.1);
results.add(Pair.of(BLMPop, buildArgs("0.1", "1", "key", "LEFT", "COUNT", "1")));

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

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