From 284badc75b3f6a6c14af1f578439ccaca46ea40c Mon Sep 17 00:00:00 2001 From: Adan Date: Thu, 7 Mar 2024 11:41:10 +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 | 21 ++++++++ node/src/Commands.ts | 55 +++++++++++---------- node/src/Transaction.ts | 22 +++++++++ node/tests/SharedTests.ts | 36 ++++++++++++++ node/tests/TestUtilities.ts | 17 +++++-- 7 files changed, 123 insertions(+), 30 deletions(-) diff --git a/glide-core/src/protobuf/redis_request.proto b/glide-core/src/protobuf/redis_request.proto index 586e8b2e16..fe1d814115 100644 --- a/glide-core/src/protobuf/redis_request.proto +++ b/glide-core/src/protobuf/redis_request.proto @@ -129,6 +129,7 @@ enum RequestType { PTTL = 85; ZRemRangeByRank = 86; Persist = 87; + ZRemRangeByScore = 88; } message Command { diff --git a/glide-core/src/socket_listener.rs b/glide-core/src/socket_listener.rs index 5760cd3ca7..0f9d2ee952 100644 --- a/glide-core/src/socket_listener.rs +++ b/glide-core/src/socket_listener.rs @@ -359,6 +359,7 @@ fn get_command(request: &Command) -> Option { RequestType::PTTL => Some(cmd("PTTL")), RequestType::ZRemRangeByRank => Some(cmd("ZREMRANGEBYRANK")), RequestType::Persist => Some(cmd("PERSIST")), + RequestType::ZRemRangeByScore => Some(cmd("ZREMRANGEBYSCORE")), } } diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts index 64d5cf18fd..a151374d0a 100644 --- a/node/src/BaseClient.ts +++ b/node/src/BaseClient.ts @@ -68,6 +68,7 @@ import { createZpopmin, createZrem, createZremRangeByRank, + createZremRangeByScore, createZscore, } from "./Commands"; import { @@ -1207,6 +1208,26 @@ export class BaseClient { return this.createWritePromise(createZremRangeByRank(key, start, end)); } + /** Removes all elements in the sorted set stored at `key` with a score between `minScore` and `maxScore`. + * 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 27d47f7e9d..87956e30b4 100644 --- a/node/src/Commands.ts +++ b/node/src/Commands.ts @@ -804,6 +804,20 @@ const positiveInfinityArg = "+inf"; const negativeInfinityArg = "-inf"; const isInclusiveArg = "("; +function getScoreLimitArg(score: ScoreLimit): string { + if (score == "positiveInfinity") { + return positiveInfinityArg; + } else if (score == "negativeInfinity") { + return negativeInfinityArg; + } + + const value = + score.isInclusive == false + ? isInclusiveArg + score.bound.toString() + : score.bound.toString(); + return value; +} + /** * @internal */ @@ -813,31 +827,8 @@ export function createZcount( 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); - } - + args.push(getScoreLimitArg(minScore)); + args.push(getScoreLimitArg(maxScore)); return createCommand(RequestType.Zcount, args); } @@ -922,3 +913,17 @@ export function createZremRangeByRank( export function createPersist(key: string): redis_request.Command { return createCommand(RequestType.Persist, [key]); } + +/** + * @internal + */ +export function createZremRangeByScore( + key: string, + minScore: ScoreLimit, + maxScore: ScoreLimit, +): redis_request.Command { + const args = [key]; + args.push(getScoreLimitArg(minScore)); + args.push(getScoreLimitArg(maxScore)); + return createCommand(RequestType.ZRemRangeByScore, args); +} diff --git a/node/src/Transaction.ts b/node/src/Transaction.ts index 72d371d09b..a9e0c0b5ae 100644 --- a/node/src/Transaction.ts +++ b/node/src/Transaction.ts @@ -71,6 +71,7 @@ import { createZpopmin, createZrem, createZremRangeByRank, + createZremRangeByScore, createZscore, } from "./Commands"; import { redis_request } from "./ProtobufMessage"; @@ -982,6 +983,27 @@ export class BaseTransaction> { return this.addAndReturn(createPersist(key)); } + /** Removes all elements in the sorted set stored at `key` with a score between `minScore` and `maxScore`. + * 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 eb42e65b95..6e47223228 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -1750,6 +1750,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 2f4b6926ce..f8a3ff421b 100644 --- a/node/tests/TestUtilities.ts +++ b/node/tests/TestUtilities.ts @@ -144,23 +144,30 @@ export function transactionTest( member2: 2, member3: 3.5, member4: 4, + member5: 5, }); - args.push(4); + args.push(5); baseTransaction.zaddIncr(key8, "member2", 1); args.push(3); baseTransaction.zrem(key8, ["member1"]); args.push(1); baseTransaction.zcard(key8); - args.push(3); + args.push(4); baseTransaction.zscore(key8, "member2"); args.push(3.0); baseTransaction.zcount(key8, { bound: 2 }, "positiveInfinity"); - args.push(3); + args.push(4); baseTransaction.zpopmin(key8); args.push({ member2: 3.0 }); baseTransaction.zpopmax(key8); - args.push({ member4: 4 }); - baseTransaction.zremRangeByRank(key8, 0, 1); + args.push({ member5: 5 }); + baseTransaction.zremRangeByRank(key8, 1, 1); + args.push(1); + baseTransaction.zremRangeByScore( + key8, + "negativeInfinity", + "positiveInfinity", + ); args.push(1); return args; }