From bca8550bf9bed211c79c292a5ee14a72d9f60a34 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Wed, 17 Jul 2024 17:47:07 -0700 Subject: [PATCH 1/2] Node: add ZDIFF command Signed-off-by: aaron-congo --- CHANGELOG.md | 1 + node/src/BaseClient.ts | 54 +++++++++++++++++++++ node/src/Commands.ts | 19 ++++++++ node/src/Transaction.ts | 36 ++++++++++++++ node/tests/RedisClusterClient.test.ts | 2 + node/tests/SharedTests.ts | 70 +++++++++++++++++++++++++++ node/tests/TestUtilities.ts | 10 +++- 7 files changed, 191 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aead2ace64..8fec9f60cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ * Node: Added LPUSHX and RPUSHX command([#1959](https://github.com/valkey-io/valkey-glide/pull/1959)) * Node: Added LSET command ([#1952](https://github.com/valkey-io/valkey-glide/pull/1952)) * Node: Added SDIFFSTORE command ([#1931](https://github.com/valkey-io/valkey-glide/pull/1931)) +* Node: Added ZDIFF command ([#1972](https://github.com/valkey-io/valkey-glide/pull/1972)) * Node: Added SINTERCARD command ([#1956](https://github.com/valkey-io/valkey-glide/pull/1956)) * Node: Added SINTERSTORE command ([#1929](https://github.com/valkey-io/valkey-glide/pull/1929)) * Node: Added SUNION command ([#1919](https://github.com/valkey-io/valkey-glide/pull/1919)) diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts index 34d4ead4fc..c955d698f7 100644 --- a/node/src/BaseClient.ts +++ b/node/src/BaseClient.ts @@ -100,6 +100,8 @@ import { createZAdd, createZCard, createZCount, + createZDiff, + createZDiffWithScores, createZInterCard, createZInterstore, createZPopMax, @@ -2020,6 +2022,58 @@ export class BaseClient { return this.createWritePromise(createZInterCard(keys, limit)); } + /** + * Returns the difference between the first sorted set and all the successive sorted sets. + * To get the elements with their scores, see {@link zdiffWithScores}. + * + * See https://valkey.io/commands/zdiff/ for more details. + * + * @remarks When in cluster mode, all `keys` must map to the same hash slot. + * @param keys - The keys of the sorted sets. + * @returns An `array` of elements representing the difference between the sorted sets. + * If the first key does not exist, it is treated as an empty sorted set, and the command returns an empty `array`. + * + * since Valkey version 6.2.0. + * + * @example + * ```typescript + * await client.zadd("zset1", {"member1": 1.0, "member2": 2.0, "member3": 3.0}); + * await client.zadd("zset2", {"member2": 2.0}); + * await client.zadd("zset3", {"member3": 3.0}); + * const result = await client.zdiff(["zset1", "zset2", "zset3"]); + * console.log(result); // Output: ["member1"] - "member1" is in "zset1" but not "zset2" or "zset3". + * ``` + */ + public zdiff(keys: string[]): Promise { + return this.createWritePromise(createZDiff(keys)); + } + + /** + * Returns the difference between the first sorted set and all the successive sorted sets, with the associated + * scores. + * + * See https://valkey.io/commands/zdiff/ for more details. + * + * @remarks When in cluster mode, all `keys` must map to the same hash slot. + * @param keys - The keys of the sorted sets. + * @returns A map of elements and their scores representing the difference between the sorted sets. + * If the first key does not exist, it is treated as an empty sorted set, and the command returns an empty `array`. + * + * since Valkey version 6.2.0. + * + * @example + * ```typescript + * await client.zadd("zset1", {"member1": 1.0, "member2": 2.0, "member3": 3.0}); + * await client.zadd("zset2", {"member2": 2.0}); + * await client.zadd("zset3", {"member3": 3.0}); + * const result = await client.zdiffWithScores(["zset1", "zset2", "zset3"]); + * console.log(result); // Output: {"member1": 1.0} - "member1" is in "zset1" but not "zset2" or "zset3". + * ``` + */ + public zdiffWithScores(keys: string[]): Promise> { + return this.createWritePromise(createZDiffWithScores(keys)); + } + /** Returns the score of `member` in the sorted set stored at `key`. * See https://valkey.io/commands/zscore/ for more details. * diff --git a/node/src/Commands.ts b/node/src/Commands.ts index c68efc25ac..cc48172922 100644 --- a/node/src/Commands.ts +++ b/node/src/Commands.ts @@ -1025,6 +1025,25 @@ export function createZInterCard( return createCommand(RequestType.ZInterCard, args); } +/** + * @internal + */ +export function createZDiff(keys: string[]): command_request.Command { + const args: string[] = keys; + args.unshift(keys.length.toString()); + return createCommand(RequestType.ZDiff, args); +} + +/** + * @internal + */ +export function createZDiffWithScores(keys: string[]): command_request.Command { + const args: string[] = keys; + args.unshift(keys.length.toString()); + args.push("WITHSCORES"); + return createCommand(RequestType.ZDiff, args); +} + /** * @internal */ diff --git a/node/src/Transaction.ts b/node/src/Transaction.ts index c72dee1686..eb91ab4849 100644 --- a/node/src/Transaction.ts +++ b/node/src/Transaction.ts @@ -108,6 +108,8 @@ import { createZAdd, createZCard, createZCount, + createZDiff, + createZDiffWithScores, createZInterCard, createZInterstore, createZPopMax, @@ -1145,6 +1147,40 @@ export class BaseTransaction> { return this.addAndReturn(createZInterCard(keys, limit)); } + /** + * Returns the difference between the first sorted set and all the successive sorted sets. + * To get the elements with their scores, see {@link zdiffWithScores}. + * + * See https://valkey.io/commands/zdiff/ for more details. + * + * @param keys - The keys of the sorted sets. + * + * Command Response - An `array` of elements representing the difference between the sorted sets. + * If the first key does not exist, it is treated as an empty sorted set, and the command returns an empty `array`. + * + * since Valkey version 6.2.0. + */ + public zdiff(keys: string[]): T { + return this.addAndReturn(createZDiff(keys)); + } + + /** + * Returns the difference between the first sorted set and all the successive sorted sets, with the associated + * scores. + * + * See https://valkey.io/commands/zdiff/ for more details. + * + * @param keys - The keys of the sorted sets. + * + * Command Response - A map of elements and their scores representing the difference between the sorted sets. + * If the first key does not exist, it is treated as an empty sorted set, and the command returns an empty `array`. + * + * since Valkey version 6.2.0. + */ + public zdiffWithScores(keys: string[]): T { + return this.addAndReturn(createZDiffWithScores(keys)); + } + /** Returns the score of `member` in the sorted set stored at `key`. * See https://valkey.io/commands/zscore/ for more details. * diff --git a/node/tests/RedisClusterClient.test.ts b/node/tests/RedisClusterClient.test.ts index 63206b3f7a..a728fcd6fc 100644 --- a/node/tests/RedisClusterClient.test.ts +++ b/node/tests/RedisClusterClient.test.ts @@ -303,6 +303,8 @@ describe("GlideClusterClient", () => { client.sintercard(["abc", "zxy", "lkn"]), client.sinterstore("abc", ["zxy", "lkn"]), client.zinterstore("abc", ["zxy", "lkn"]), + client.zdiff(["abc", "zxy", "lkn"]), + client.zdiffWithScores(["abc", "zxy", "lkn"]), client.sunionstore("abc", ["zxy", "lkn"]), client.sunion(["abc", "zxy", "lkn"]), client.pfcount(["abc", "zxy", "lkn"]), diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts index a8740143a9..4bed468ad8 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -2168,6 +2168,76 @@ export function runBaseTests(config: { config.timeout, ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zdiff test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + if (await checkIfServerVersionLessThan("6.2.0")) { + return; + } + + const key1 = `{key}-${uuidv4()}`; + const key2 = `{key}-${uuidv4()}`; + const key3 = `{key}-${uuidv4()}`; + const nonExistingKey = `{key}-${uuidv4()}`; + const stringKey = `{key}-${uuidv4()}`; + + const entries1 = { + one: 1.0, + two: 2.0, + three: 3.0, + }; + const entries2 = { two: 2.0 }; + const entries3 = { + one: 1.0, + two: 2.0, + three: 3.0, + four: 4.0, + }; + + expect(await client.zadd(key1, entries1)).toEqual(3); + expect(await client.zadd(key2, entries2)).toEqual(1); + expect(await client.zadd(key3, entries3)).toEqual(4); + + checkSimple(await client.zdiff([key1, key2])).toEqual([ + "one", + "three", + ]); + checkSimple(await client.zdiff([key1, key3])).toEqual([]); + checkSimple(await client.zdiff([nonExistingKey, key3])).toEqual( + [], + ); + + let result = await client.zdiffWithScores([key1, key2]); + let expected = { + one: 1.0, + three: 3.0, + }; + expect(compareMaps(result, expected)).toBe(true); + + result = await client.zdiffWithScores([key1, key3]); + expect(compareMaps(result, {})).toBe(true); + + result = await client.zdiffWithScores([nonExistingKey, key3]); + expect(compareMaps(result, {})).toBe(true); + + // invalid arg - key list must not be empty + await expect(client.zdiff([])).rejects.toThrow(RequestError); + await expect(client.zdiffWithScores([])).rejects.toThrow( + RequestError, + ); + + // key exists, but it is not a sorted set + checkSimple(await client.set(stringKey, "foo")).toEqual("OK"); + await expect(client.zdiff([stringKey, key1])).rejects.toThrow(); + await expect( + client.zdiffWithScores([stringKey, key1]), + ).rejects.toThrow(); + }, protocol); + }, + config.timeout, + ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( `zscore test_%p`, async (protocol) => { diff --git a/node/tests/TestUtilities.ts b/node/tests/TestUtilities.ts index d9568a332f..aecdc9b943 100644 --- a/node/tests/TestUtilities.ts +++ b/node/tests/TestUtilities.ts @@ -469,8 +469,16 @@ export async function transactionTest( args.push({ member2: 3, member3: 3.5, member4: 4, member5: 5 }); baseTransaction.zadd(key12, { one: 1, two: 2 }); args.push(2); - baseTransaction.zadd(key13, { one: 1, two: 2, tree: 3.5 }); + baseTransaction.zadd(key13, { one: 1, two: 2, three: 3.5 }); args.push(3); + + if (!(await checkIfServerVersionLessThan("6.2.0"))) { + baseTransaction.zdiff([key13, key12]); + args.push(["three"]); + baseTransaction.zdiffWithScores([key13, key12]); + args.push({ three: 3.5 }); + } + baseTransaction.zinterstore(key12, [key12, key13]); args.push(2); baseTransaction.zcount(key8, { value: 2 }, "positiveInfinity"); From 70f441ef340af57ebe46bac3607fa9eb46577de7 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Wed, 17 Jul 2024 17:55:24 -0700 Subject: [PATCH 2/2] Fix ts-lint Signed-off-by: aaron-congo --- node/tests/SharedTests.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts index 4bed468ad8..230d8e5240 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -2209,7 +2209,7 @@ export function runBaseTests(config: { ); let result = await client.zdiffWithScores([key1, key2]); - let expected = { + const expected = { one: 1.0, three: 3.0, };