From 906c43836d136e1b59856350c7615224c482c7a3 Mon Sep 17 00:00:00 2001 From: Adan Date: Thu, 7 Mar 2024 11:41:10 +0000 Subject: [PATCH 1/2] 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 | 57 +++++++++++---------- node/src/Transaction.ts | 22 ++++++++ node/tests/SharedTests.ts | 36 +++++++++++++ node/tests/TestUtilities.ts | 17 ++++-- 7 files changed, 122 insertions(+), 33 deletions(-) diff --git a/glide-core/src/protobuf/redis_request.proto b/glide-core/src/protobuf/redis_request.proto index 4761aadafb..805eae1f3e 100644 --- a/glide-core/src/protobuf/redis_request.proto +++ b/glide-core/src/protobuf/redis_request.proto @@ -131,6 +131,7 @@ enum RequestType { Persist = 87; JsonSet = 88; JsonGet = 89; + ZRemRangeByScore = 90; } message Command { diff --git a/glide-core/src/socket_listener.rs b/glide-core/src/socket_listener.rs index c7c8c0c85a..d64f890232 100644 --- a/glide-core/src/socket_listener.rs +++ b/glide-core/src/socket_listener.rs @@ -361,6 +361,7 @@ fn get_command(request: &Command) -> Option { RequestType::Persist => Some(cmd("PERSIST")), RequestType::JsonSet => Some(cmd("JSON.SET")), RequestType::JsonGet => Some(cmd("JSON.GET")), + RequestType::ZRemRangeByScore => Some(cmd("ZREMRANGEBYSCORE")), } } diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts index e5f8bfdefe..cdc8c26c90 100644 --- a/node/src/BaseClient.ts +++ b/node/src/BaseClient.ts @@ -72,6 +72,7 @@ import { createZpopmin, createZrem, createZremRangeByRank, + createZremRangeByScore, createZscore, } from "./Commands"; import { @@ -1211,6 +1212,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), + ); + } + /** * Adds an entry to the specified stream. * See https://redis.io/commands/xadd/ for more details. diff --git a/node/src/Commands.ts b/node/src/Commands.ts index aa8c4fbbf7..5d8bb73d23 100644 --- a/node/src/Commands.ts +++ b/node/src/Commands.ts @@ -800,9 +800,19 @@ export type ScoreLimit = isInclusive?: boolean; }; -const positiveInfinityArg = "+inf"; -const negativeInfinityArg = "-inf"; -const isInclusiveArg = "("; +function getScoreLimitArg(score: ScoreLimit): string { + if (score == "positiveInfinity") { + return "+inf"; + } else if (score == "negativeInfinity") { + return "-inf"; + } + + const value = + score.isInclusive == false + ? "(" + score.bound.toString() + : score.bound.toString(); + return value; +} /** * @internal @@ -813,31 +823,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); } @@ -916,6 +903,20 @@ export function createZremRangeByRank( ]); } +/** + * @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); +} + export function createPersist(key: string): redis_request.Command { return createCommand(RequestType.Persist, [key]); } diff --git a/node/src/Transaction.ts b/node/src/Transaction.ts index df0371a41c..5aa86f6c94 100644 --- a/node/src/Transaction.ts +++ b/node/src/Transaction.ts @@ -75,6 +75,7 @@ import { createZpopmin, createZrem, createZremRangeByRank, + createZremRangeByScore, createZscore, } from "./Commands"; import { redis_request } from "./ProtobufMessage"; @@ -974,6 +975,27 @@ export class BaseTransaction> { return this.addAndReturn(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. + * + * 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), + ); + } + /** Remove the existing timeout on `key`, turning the key from volatile (a key with an expire set) to * persistent (a key that will never expire as no timeout is associated). * See https://redis.io/commands/persist/ for more details. diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts index 84c10c0aa9..bfabf0b2ed 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -1829,6 +1829,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 fe806a3753..1d1203975b 100644 --- a/node/tests/TestUtilities.ts +++ b/node/tests/TestUtilities.ts @@ -145,23 +145,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); baseTransaction.xadd(key9, [["foo", "bar"]], { id: "0-1" }); args.push("0-1"); From e26fc6378eba02a2ac5ff1b7ae39afa07dc5ce68 Mon Sep 17 00:00:00 2001 From: Adan Date: Thu, 7 Mar 2024 13:37:36 +0000 Subject: [PATCH 2/2] update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e98c61f18..b39e04aa32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ * Python, Node: Added PTTL command ([#1036](https://github.com/aws/glide-for-redis/pull/1036), [#1082](https://github.com/aws/glide-for-redis/pull/1082)) * Node: Added HVAL command ([#1022](https://github.com/aws/glide-for-redis/pull/1022)) * Node: Added PERSIST command ([#1023](https://github.com/aws/glide-for-redis/pull/1023)) +* Node: Added ZREMRANGEBYSCORE command ([#926](https://github.com/aws/glide-for-redis/pull/926)) +* Node: Added ZREMRANGEBYRANK command ([#924](https://github.com/aws/glide-for-redis/pull/924)) * Node: Added Xadd, Xtrim commands. ([#1057](https://github.com/aws/glide-for-redis/pull/1057)) * Python: Added json module and JSON.SET JSON.GET commands ([#1056](https://github.com/aws/glide-for-redis/pull/1056))