Skip to content

Commit

Permalink
Node - Added BLPOP command (valkey-io#1223)
Browse files Browse the repository at this point in the history
* Node - Added BLPOP command

* Addressed PR comments

* Ran prettier

* Added unit test and ran prettier

* PR comments.

Signed-off-by: Yury-Fridlyand <[email protected]>

* Added same-slot requirement for BLPOP and addressed PR comments

* Fixed branch conflicts

* Addressed PR commands

* Update docs and test, fix merge errors.

Signed-off-by: Yury-Fridlyand <[email protected]>

* Update doc.

Signed-off-by: Yury-Fridlyand <[email protected]>

---------

Signed-off-by: Yury-Fridlyand <[email protected]>
Co-authored-by: Yury-Fridlyand <[email protected]>
Co-authored-by: Andrew Carbonetto <[email protected]>
  • Loading branch information
3 people authored May 6, 2024
1 parent 44f8221 commit d98b056
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
* Python: Added PFADD command ([#1315](https://github.com/aws/glide-for-redis/pull/1315))
* Python: Added ZMSCORE command ([#1357](https://github.com/aws/glide-for-redis/pull/1357))
* Python: Added HRANDFIELD command ([#1334](https://github.com/aws/glide-for-redis/pull/1334))
* Node: Added BLPOP command ([#1223](https://github.com/aws/glide-for-redis/pull/1223))
* Python: Added XADD, XTRIM commands ([#1320](https://github.com/aws/glide-for-redis/pull/1320))
* Python: Added ZRANGESTORE command ([#1377](https://github.com/aws/glide-for-redis/pull/1377))

Expand Down
37 changes: 35 additions & 2 deletions node/src/BaseClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
StreamReadOptions,
StreamTrimOptions,
ZaddOptions,
createBlpop,
createBrpop,
createDecr,
createDecrBy,
Expand Down Expand Up @@ -2190,13 +2191,16 @@ export class BaseClient {
* with the given keys being checked in the order that they are given.
* Blocks the connection when there are no elements to pop from any of the given lists.
* See https://redis.io/commands/brpop/ for more details.
* Note: BRPOP is a blocking command,
*
* Notes:
* 1. `BRPOP` is a blocking command,
* see [Blocking Commands](https://github.com/aws/glide-for-redis/wiki/General-Concepts#blocking-commands) for more details and best practices.
* 2. When in cluster mode, all `keys` must map to the same `hash slot`.
*
* @param keys - The `keys` of the lists to pop from.
* @param timeout - The `timeout` in seconds.
* @returns - An `array` containing the `key` from which the element was popped and the value of the popped element,
* formatted as [key, value]. If no element could be popped and the timeout expired, returns Null.
* formatted as [key, value]. If no element could be popped and the timeout expired, returns `null`.
*
* @example
* ```typescript
Expand All @@ -2212,6 +2216,35 @@ export class BaseClient {
return this.createWritePromise(createBrpop(keys, timeout));
}

/** Blocking list pop primitive.
* Pop an element from the head of the first list that is non-empty,
* with the given `keys` being checked in the order that they are given.
* Blocks the connection when there are no elements to pop from any of the given lists.
* See https://redis.io/commands/blpop/ for more details.
*
* Notes:
* 1. `BLPOP` is a blocking command,
* see [Blocking Commands](https://github.com/aws/glide-for-redis/wiki/General-Concepts#blocking-commands) for more details and best practices.
* 2. When in cluster mode, all `keys` must map to the same `hash slot`.
*
* @param keys - The `keys` of the lists to pop from.
* @param timeout - The `timeout` in seconds.
* @returns - An `array` containing the `key` from which the element was popped and the value of the popped element,
* formatted as [key, value]. If no element could be popped and the timeout expired, returns `null`.
*
* @example
* ```typescript
* const result = await client.blpop(["list1", "list2"], 5);
* console.log(result); // Output: ['list1', 'element']
* ```
*/
public blpop(
keys: string[],
timeout: number,
): Promise<[string, string] | null> {
return this.createWritePromise(createBlpop(keys, timeout));
}

/** Adds all elements to the HyperLogLog data structure stored at the specified `key`.
* Creates a new structure if the `key` does not exist.
* When no elements are provided, and `key` exists and is a HyperLogLog, then no operation is performed.
Expand Down
11 changes: 11 additions & 0 deletions node/src/Commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1221,6 +1221,17 @@ export function createBrpop(
return createCommand(RequestType.Brpop, args);
}

/**
* @internal
*/
export function createBlpop(
keys: string[],
timeout: number,
): redis_request.Command {
const args = [...keys, timeout.toString()];
return createCommand(RequestType.Blpop, args);
}

export type StreamReadOptions = {
/**
* If set, the read request will block for the set amount of milliseconds or until the server has the required number of entries.
Expand Down
22 changes: 20 additions & 2 deletions node/src/Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
StreamReadOptions,
StreamTrimOptions,
ZaddOptions,
createBlpop,
createBrpop,
createClientGetName,
createClientId,
Expand Down Expand Up @@ -1243,18 +1244,35 @@ export class BaseTransaction<T extends BaseTransaction<T>> {
* with the given keys being checked in the order that they are given.
* Blocks the connection when there are no elements to pop from any of the given lists.
* See https://redis.io/commands/brpop/ for more details.
* Note: BRPOP is a blocking command,
* Note: `BRPOP` is a blocking command,
* see [Blocking Commands](https://github.com/aws/glide-for-redis/wiki/General-Concepts#blocking-commands) for more details and best practices.
*
* @param keys - The `keys` of the lists to pop from.
* @param timeout - The `timeout` in seconds.
* Command Response - An `array` containing the `key` from which the element was popped and the value of the popped element,
* formatted as [key, value]. If no element could be popped and the timeout expired, returns Null.
* formatted as [key, value]. If no element could be popped and the timeout expired, returns `null`.
*/
public brpop(keys: string[], timeout: number): T {
return this.addAndReturn(createBrpop(keys, timeout));
}

/** Blocking list pop primitive.
* Pop an element from the head of the first list that is non-empty,
* with the given `keys` being checked in the order that they are given.
* Blocks the connection when there are no elements to pop from any of the given lists.
* See https://redis.io/commands/blpop/ for more details.
* Note: `BLPOP` is a blocking command,
* see [Blocking Commands](https://github.com/aws/glide-for-redis/wiki/General-Concepts#blocking-commands) for more details and best practices.
*
* @param keys - The `keys` of the lists to pop from.
* @param timeout - The `timeout` in seconds.
* Command Response - An `array` containing the `key` from which the element was popped and the value of the popped element,
* formatted as [key, value]. If no element could be popped and the timeout expired, returns `null`.
*/
public blpop(keys: string[], timeout: number): T {
return this.addAndReturn(createBlpop(keys, timeout));
}

/** Adds all elements to the HyperLogLog data structure stored at the specified `key`.
* Creates a new structure if the `key` does not exist.
* When no elements are provided, and `key` exists and is a HyperLogLog, then no operation is performed.
Expand Down
53 changes: 53 additions & 0 deletions node/tests/SharedTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2049,6 +2049,59 @@ export function runBaseTests<Context>(config: {
expect(await client.del(["brpop-test"])).toEqual(1);
// Test null return when key doesn't exist
expect(await client.brpop(["brpop-test"], 0.1)).toEqual(null);
// key exists, but it is not a list
await client.set("foo", "bar");
await expect(client.brpop(["foo"], 0.1)).rejects.toThrow();

// Same-slot requirement
if (client instanceof RedisClusterClient) {
try {
expect(
await client.brpop(["abc", "zxy", "lkn"], 0.1),
).toThrow();
} catch (e) {
expect((e as Error).message.toLowerCase()).toMatch(
"crossslot",
);
}
}
}, protocol);
},
config.timeout,
);

it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
`test blpop test_%p`,
async (protocol) => {
await runTest(async (client: BaseClient) => {
expect(
await client.rpush("blpop-test", ["foo", "bar", "baz"]),
).toEqual(3);
// Test basic usage
expect(await client.blpop(["blpop-test"], 0.1)).toEqual([
"blpop-test",
"foo",
]);
// Delete all values from list
expect(await client.del(["blpop-test"])).toEqual(1);
// Test null return when key doesn't exist
expect(await client.blpop(["blpop-test"], 0.1)).toEqual(null);
// key exists, but it is not a list
await client.set("foo", "bar");
await expect(client.blpop(["foo"], 0.1)).rejects.toThrow();

// Same-slot requirement
if (client instanceof RedisClusterClient) {
try {
expect(
await client.blpop(["abc", "zxy", "lkn"], 0.1),
).toThrow();
} catch (e) {
expect((e as Error).message.toLowerCase()).toMatch(
"crossslot",
);
}
}
}, protocol);
},
config.timeout,
Expand Down
2 changes: 2 additions & 0 deletions node/tests/TestUtilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,8 @@ export async function transactionTest(
args.push(3);
baseTransaction.brpop([key6], 0.1);
args.push([key6, field + "3"]);
baseTransaction.blpop([key6], 0.1);
args.push([key6, field + "1"]);
baseTransaction.pfadd(key11, ["a", "b", "c"]);
args.push(1);
return args;
Expand Down

0 comments on commit d98b056

Please sign in to comment.