From c5bffdfe98a39fe3fa13f22a8f08d036b233aa81 Mon Sep 17 00:00:00 2001 From: Adan Date: Wed, 21 Feb 2024 09:06:41 +0000 Subject: [PATCH] Added zremRangeByScore command in Node. --- glide-core/src/protobuf/redis_request.proto | 1 + glide-core/src/socket_listener.rs | 1 + node/src/BaseClient.ts | 19 +++++++++++ node/src/Commands.ts | 37 +++++++++++++++++++++ node/src/Transaction.ts | 20 +++++++++++ node/tests/SharedTests.ts | 36 ++++++++++++++++++++ node/tests/TestUtilities.ts | 12 ++++--- 7 files changed, 121 insertions(+), 5 deletions(-) diff --git a/glide-core/src/protobuf/redis_request.proto b/glide-core/src/protobuf/redis_request.proto index ceaa83c11a..fa03ab91f9 100644 --- a/glide-core/src/protobuf/redis_request.proto +++ b/glide-core/src/protobuf/redis_request.proto @@ -127,6 +127,7 @@ enum RequestType { SIsMember = 83; Hvals = 84; PTTL = 85; + ZRemRangeByScore = 86; } message Command { diff --git a/glide-core/src/socket_listener.rs b/glide-core/src/socket_listener.rs index dfea685be6..6456a5441f 100644 --- a/glide-core/src/socket_listener.rs +++ b/glide-core/src/socket_listener.rs @@ -364,6 +364,7 @@ fn get_command(request: &Command) -> Option { RequestType::SIsMember => Some(cmd("SISMEMBER")), RequestType::Hvals => Some(cmd("HVALS")), RequestType::PTTL => Some(cmd("PTTL")), + RequestType::ZRemRangeByScore => Some(cmd("ZREMRANGEBYSCORE")), } } diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts index 61454210d0..3354b08473 100644 --- a/node/src/BaseClient.ts +++ b/node/src/BaseClient.ts @@ -65,6 +65,7 @@ import { createZpopmax, createZpopmin, createZrem, + createZremRangeByScore, createZscore, } from "./Commands"; import { @@ -1171,6 +1172,24 @@ export class BaseClient { return this.createWritePromise(createPttl(key)); } + /** Removes all elements in the sorted set stored at `key` with a score between `minScore` and `maxScore` (inclusive). + * See https://redis.io/commands/zremrangebyscore/ for more details. + * + * @param key - The key of the sorted set. + * @param minScore - The minimum score to remove from. Can be positive/negative infinity, or specific score and inclusivity. + * @param maxScore - The maximum score to remove to. Can be positive/negative infinity, or specific score and inclusivity. + * @returns the number of members removed. + * If `key` does not exist, it is treated as an empty sorted set, and the command returns 0. + * If `minScore` is greater than `maxScore`, 0 is returned. + */ + public zremRangeByScore( + key: string, + minScore: ScoreLimit, + maxScore: ScoreLimit + ): Promise { + return this.createWritePromise(createZremRangeByScore(key, minScore, maxScore)); + } + private readonly MAP_READ_FROM_STRATEGY: Record< ReadFrom, connection_request.ReadFrom diff --git a/node/src/Commands.ts b/node/src/Commands.ts index b70685b001..72736826c1 100644 --- a/node/src/Commands.ts +++ b/node/src/Commands.ts @@ -884,3 +884,40 @@ export function createEcho(message: string): redis_request.Command { export function createPttl(key: string): redis_request.Command { return createCommand(RequestType.PTTL, [key]); } + +/** + * @internal + */ +export function createZremRangeByScore( + key: string, + minScore: ScoreLimit, + maxScore: ScoreLimit +): redis_request.Command { + const args = [key]; + + if (minScore == "positiveInfinity") { + args.push(positiveInfinityArg); + } else if (minScore == "negativeInfinity") { + args.push(negativeInfinityArg); + } else { + const value = + minScore.isInclusive == false + ? isInclusiveArg + minScore.bound.toString() + : minScore.bound.toString(); + args.push(value); + } + + if (maxScore == "positiveInfinity") { + args.push(positiveInfinityArg); + } else if (maxScore == "negativeInfinity") { + args.push(negativeInfinityArg); + } else { + const value = + maxScore.isInclusive == false + ? isInclusiveArg + maxScore.bound.toString() + : maxScore.bound.toString(); + args.push(value); + } + + return createCommand(RequestType.ZRemRangeByScore, args); +} diff --git a/node/src/Transaction.ts b/node/src/Transaction.ts index de8a087703..a22efbaf5d 100644 --- a/node/src/Transaction.ts +++ b/node/src/Transaction.ts @@ -68,6 +68,7 @@ import { createZpopmax, createZpopmin, createZrem, + createZremRangeByScore, createZscore, } from "./Commands"; import { redis_request } from "./ProtobufMessage"; @@ -936,6 +937,25 @@ export class BaseTransaction> { return this.addAndReturn(createPttl(key)); } + /** Removes all elements in the sorted set stored at `key` with a score between `minScore` and `maxScore` (inclusive). + * See https://redis.io/commands/zremrangebyscore/ for more details. + * + * @param key - The key of the sorted set. + * @param minScore - The minimum score to remove from. Can be positive/negative infinity, or specific score and inclusivity. + * @param maxScore - The maximum score to remove to. Can be positive/negative infinity, or specific score and inclusivity. + * + * Command Response - the number of members removed. + * If `key` does not exist, it is treated as an empty sorted set, and the command returns 0. + * If `minScore` is greater than `maxScore`, 0 is returned. + */ + public zremRangeByScore( + key: string, + minScore: ScoreLimit, + maxScore: ScoreLimit + ): T { + return this.addAndReturn(createZremRangeByScore(key, minScore, maxScore)); + } + /** Executes a single command, without checking inputs. Every part of the command, including subcommands, * should be added as a separate value in args. * diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts index 3920334eb4..42862fd5af 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -1680,6 +1680,42 @@ export function runBaseTests(config: { }, config.timeout ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zremRangeByScore test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + const membersScores = { one: 1, two: 2, three: 3 }; + expect(await client.zadd(key, membersScores)).toEqual(3); + + expect( + await client.zremRangeByScore( + key, + { bound: 1, isInclusive: false }, + { bound: 2 } + ) + ).toEqual(1); + + expect( + await client.zremRangeByScore( + key, + { bound: 1 }, + "negativeInfinity" + ) + ).toEqual(0); + + expect( + await client.zremRangeByScore( + "nonExistingKey", + "negativeInfinity", + "positiveInfinity" + ) + ).toEqual(0); + }, protocol); + }, + config.timeout + ); } export function runCommonTests(config: { diff --git a/node/tests/TestUtilities.ts b/node/tests/TestUtilities.ts index f468e83921..9961281f3d 100644 --- a/node/tests/TestUtilities.ts +++ b/node/tests/TestUtilities.ts @@ -135,22 +135,24 @@ export function transactionTest( args.push(1); baseTransaction.smembers(key7); args.push(["bar"]); - baseTransaction.zadd(key8, { member1: 1, member2: 2, member3: 3.5 }); - args.push(3); + baseTransaction.zadd(key8, { member1: 1, member2: 2, member3: 3.5, member4: 4 }); + args.push(4); baseTransaction.zaddIncr(key8, "member2", 1); args.push(3); baseTransaction.zrem(key8, ["member1"]); args.push(1); baseTransaction.zcard(key8); - args.push(2); + args.push(3); baseTransaction.zscore(key8, "member2"); args.push(3.0); baseTransaction.zcount(key8, { bound: 2 }, "positiveInfinity"); - args.push(2); + args.push(3); baseTransaction.zpopmin(key8); args.push({ member2: 3.0 }); baseTransaction.zpopmax(key8); - args.push({ member3: 3.5 }); + args.push({ member4: 4 }); + baseTransaction.zremRangeByScore(key8, "negativeInfinity", "positiveInfinity"); + args.push(1); return args; }