From a7ed20f6b2ad8161d49e025a7c2cfab10d6cfb7a Mon Sep 17 00:00:00 2001 From: TJ Zhang Date: Thu, 15 Aug 2024 14:16:04 -0700 Subject: [PATCH] Node: add ZINTER and ZUNION commands Signed-off-by: TJ Zhang --- CHANGELOG.md | 1 + node/src/BaseClient.ts | 130 +++++++++- node/src/Commands.ts | 47 +++- node/src/Transaction.ts | 84 +++++++ node/tests/GlideClusterClient.test.ts | 4 + node/tests/SharedTests.ts | 342 ++++++++++++++++++++++++++ node/tests/TestUtilities.ts | 24 ++ 7 files changed, 626 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12854040b7..352b7a2f11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,6 +79,7 @@ * Node: Added BZPOPMAX & BZPOPMIN command ([#2077]((https://github.com/valkey-io/valkey-glide/pull/2077)) * Node: Added XGROUP CREATECONSUMER & XGROUP DELCONSUMER commands ([#2088](https://github.com/valkey-io/valkey-glide/pull/2088)) * Node: Added GETEX command ([#2107]((https://github.com/valkey-io/valkey-glide/pull/2107)) +* Node: Added ZINTER and ZUNION commands ([#2146](https://github.com/aws/glide-for-redis/pull/2146)) #### Breaking Changes * Node: (Refactor) Convert classes to types ([#2005](https://github.com/valkey-io/valkey-glide/pull/2005)) diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts index 4dc45abe4a..8aae54013e 100644 --- a/node/src/BaseClient.ts +++ b/node/src/BaseClient.ts @@ -188,6 +188,7 @@ import { createZDiffStore, createZDiffWithScores, createZIncrBy, + createZInter, createZInterCard, createZInterstore, createZLexCount, @@ -208,6 +209,7 @@ import { createZRevRankWithScore, createZScan, createZScore, + createZUnion, } from "./Commands"; import { ClosingError, @@ -3627,9 +3629,9 @@ export class BaseClient { * await client.zadd("key1", {"member1": 10.5, "member2": 8.2}) * await client.zadd("key2", {"member1": 9.5}) * await client.zinterstore("my_sorted_set", ["key1", "key2"]) // Output: 1 - Indicates that the sorted set "my_sorted_set" contains one element. - * await client.zrange_withscores("my_sorted_set", RangeByIndex(0, -1)) // Output: {'member1': 20} - "member1" is now stored in "my_sorted_set" with score of 20. + * await client.zrangeWithScores("my_sorted_set", RangeByIndex(0, -1)) // Output: {'member1': 20} - "member1" is now stored in "my_sorted_set" with score of 20. * await client.zinterstore("my_sorted_set", ["key1", "key2"] , AggregationType.MAX ) // Output: 1 - Indicates that the sorted set "my_sorted_set" contains one element, and it's score is the maximum score between the sets. - * await client.zrange_withscores("my_sorted_set", RangeByIndex(0, -1)) // Output: {'member1': 10.5} - "member1" is now stored in "my_sorted_set" with score of 10.5. + * await client.zrangeWithsScores("my_sorted_set", RangeByIndex(0, -1)) // Output: {'member1': 10.5} - "member1" is now stored in "my_sorted_set" with score of 10.5. * ``` */ public async zinterstore( @@ -3642,6 +3644,130 @@ export class BaseClient { ); } + /** + * Computes the intersection of sorted sets given by the specified `keys` and returns a list of intersecting elements. + * To get the scores as well, see `zinterWithScores`. + * To store the result in a key as a sorted set, see `zinterStore`. + * + * @remarks When in cluster mode, all keys in `keys` must map to the same hash slot. + * + * since - Valkey version 6.2.0. + * + * @see {@link https://valkey.io/commands/zinter/|valkey.io} for details. + * + * @param keys - The keys of the sorted sets. + * @returns The resulting array of intersecting elements. + * + * @example + * ```typescript + * await client.zadd("key1", {"member1": 10.5, "member2": 8.2}); + * await client.zadd("key2", {"member1": 9.5}); + * const result = await client.zinter(["key1", "key2"]); + * console.log(result); // Output: ['member1'] + * ``` + */ + public zinter(keys: string[]): Promise { + return this.createWritePromise(createZInter(keys)); + } + + /** + * Computes the intersection of sorted sets given by the specified `keys` and returns a list of intersecting elements with scores. + * To get the elements only, see `zinter`. + * To store the result in a key as a sorted set, see `zinterStore`. + * + * @remarks When in cluster mode, all keys in `keys` must map to the same hash slot. + * + * @see {@link https://valkey.io/commands/zinter/|valkey.io} for details. + * + * since Valkey version 6.2.0. + * + * @param keys - The keys of the sorted sets with possible formats: + * - string[] - for keys only. + * - KeyWeight[] - for weighted keys with score multipliers. + * @param aggregationType - Specifies the aggregation strategy to apply when combining the scores of elements. See `AggregationType`. + * @returns The resulting sorted set with scores. + * + * @example + * ```typescript + * await client.zadd("key1", {"member1": 10.5, "member2": 8.2}); + * await client.zadd("key2", {"member1": 9.5}); + * const result1 = await client.zinterWithScores(["key1", "key2"]); + * console.log(result1); // Output: {'member1': 20} - "member1" with score of 20 is the result + * const result2 = await client.zinterWithScores(["key1", "key2"], AggregationType.MAX) + * console.log(result2); // Output: {'member1': 10.5} - "member1" with score of 10.5 is the result. + * ``` + */ + public zinterWithScores( + keys: string[] | KeyWeight[], + aggregationType?: AggregationType, + ): Promise> { + return this.createWritePromise( + createZInter(keys, aggregationType, true), + ); + } + + /** + * Computes the union of sorted sets given by the specified `keys` and returns a list of union elements. + * To get the scores as well, see `zunionWithScores`. + * + * To store the result in a key as a sorted set, see `zunionStore`. + * + * @remarks When in cluster mode, all keys in `keys` must map to the same hash slot. + * + * since - Valkey version 6.2.0. + * + * @see {@link https://valkey.io/commands/zunion/|valkey.io} for details. + * + * @param keys - The keys of the sorted sets. + * @returns The resulting array of union elements. + * + * @example + * ```typescript + * await client.zadd("key1", {"member1": 10.5, "member2": 8.2}); + * await client.zadd("key2", {"member1": 9.5}); + * const result = await client.zunion(["key1", "key2"]); + * console.log(result); // Output: ['member1', 'member2'] + * ``` + */ + public zunion(keys: string[]): Promise { + return this.createWritePromise(createZUnion(keys)); + } + + /** + * Computes the intersection of sorted sets given by the specified `keys` and returns a list of union elements with scores. + * To get the elements only, see `zunion`. + * + * @remarks When in cluster mode, all keys in `keys` must map to the same hash slot. + * + * @see {@link https://valkey.io/commands/zunion/|valkey.io} for details. + * + * since Valkey version 6.2.0. + * + * @param keys - The keys of the sorted sets with possible formats: + * - string[] - for keys only. + * - KeyWeight[] - for weighted keys with score multipliers. + * @param aggregationType - Specifies the aggregation strategy to apply when combining the scores of elements. See `AggregationType`. + * @returns The resulting sorted set with scores. + * + * @example + * ```typescript + * await client.zadd("key1", {"member1": 10.5, "member2": 8.2}); + * await client.zadd("key2", {"member1": 9.5}); + * const result1 = await client.zunionWithScores(["key1", "key2"]); + * console.log(result1); // {'member1': 20, 'member2': 8.2} + * const result2 = await client.zunionWithScores(["key1", "key2"], "MAX"); + * console.log(result2); // {'member1': 10.5, 'member2': 8.2} + * ``` + */ + public zunionWithScores( + keys: string[] | KeyWeight[], + aggregationType?: AggregationType, + ): Promise> { + return this.createWritePromise( + createZUnion(keys, aggregationType, true), + ); + } + /** * Returns a random member from the sorted set stored at `key`. * diff --git a/node/src/Commands.ts b/node/src/Commands.ts index 8a6fd01ae0..3cc83f62f0 100644 --- a/node/src/Commands.ts +++ b/node/src/Commands.ts @@ -1424,16 +1424,51 @@ export function createZInterstore( keys: string[] | KeyWeight[], aggregationType?: AggregationType, ): command_request.Command { - const args = createZCmdStoreArgs(destination, keys, aggregationType); + const args = createZCmdArgs(keys, aggregationType, false, destination); return createCommand(RequestType.ZInterStore, args); } -function createZCmdStoreArgs( - destination: string, +/** + * @internal + */ +export function createZInter( + keys: string[] | KeyWeight[], + aggregationType?: AggregationType, + withScores?: boolean, +): command_request.Command { + const args = createZCmdArgs(keys, aggregationType, withScores); + return createCommand(RequestType.ZInter, args); +} + +/** + * @internal + */ +export function createZUnion( keys: string[] | KeyWeight[], aggregationType?: AggregationType, + withScores?: boolean, +): command_request.Command { + const args = createZCmdArgs(keys, aggregationType, withScores); + return createCommand(RequestType.ZUnion, args); +} + +/** + * @internal + * Helper function for Zcommands (ZInter, ZinterStore, ZUnion..) that arranges arguments in the server's required order. + */ +function createZCmdArgs( + keys: string[] | KeyWeight[], + aggregationType?: AggregationType, + withscores?: boolean, + destination?: string, ): string[] { - const args: string[] = [destination, keys.length.toString()]; + const args = []; + + if (destination) { + args.push(destination); + } + + args.push(keys.length.toString()); if (typeof keys[0] === "string") { args.push(...(keys as string[])); @@ -1448,6 +1483,10 @@ function createZCmdStoreArgs( args.push("AGGREGATE", aggregationType); } + if (withscores) { + args.push("WITHSCORES"); + } + return args; } diff --git a/node/src/Transaction.ts b/node/src/Transaction.ts index c7b7746158..4213057f42 100644 --- a/node/src/Transaction.ts +++ b/node/src/Transaction.ts @@ -224,6 +224,7 @@ import { createZDiffStore, createZDiffWithScores, createZIncrBy, + createZInter, createZInterCard, createZInterstore, createZLexCount, @@ -244,6 +245,7 @@ import { createZRevRankWithScore, createZScan, createZScore, + createZUnion, } from "./Commands"; import { command_request } from "./ProtobufMessage"; @@ -1871,6 +1873,8 @@ export class BaseTransaction> { * * @see {@link https://valkey.io/commands/zinterstore/|valkey.io} for details. * + * since Valkey version 6.2.0. + * * @param destination - The key of the destination sorted set. * @param keys - The keys of the sorted sets with possible formats: * string[] - for keys only. @@ -1888,6 +1892,86 @@ export class BaseTransaction> { ); } + /** + * Computes the intersection of sorted sets given by the specified `keys` and returns a list of intersecting elements. + * To get the scores as well, see `zinterWithScores`. + * To store the result in a key as a sorted set, see `zinterStore`. + * + * since - Valkey version 6.2.0. + * + * @see {@link https://valkey.io/commands/zinter/|valkey.io} for details. + * + * @param keys - The keys of the sorted sets. + * + * Command Response - The resulting array of intersecting elements. + */ + public zinter(keys: string[]): T { + return this.addAndReturn(createZInter(keys)); + } + + /** + * Computes the intersection of sorted sets given by the specified `keys` and returns a list of intersecting elements with scores. + * To get the elements only, see `zinter`. + * To store the result in a key as a sorted set, see `zinterStore`. + * + * @see {@link https://valkey.io/commands/zinter/|valkey.io} for details. + * + * since Valkey version 6.2.0. + * + * @param keys - The keys of the sorted sets with possible formats: + * - string[] - for keys only. + * - KeyWeight[] - for weighted keys with score multipliers. + * @param aggregationType - Specifies the aggregation strategy to apply when combining the scores of elements. See `AggregationType`. + * + * Command Response - The resulting sorted set with scores. + */ + public zinterWithScores( + keys: string[] | KeyWeight[], + aggregationType?: AggregationType, + ): T { + return this.addAndReturn(createZInter(keys, aggregationType, true)); + } + + /** + * Computes the union of sorted sets given by the specified `keys` and returns a list of union elements. + * To get the scores as well, see `zunionWithScores`. + * + * To store the result in a key as a sorted set, see `zunionStore`. + * + * since - Valkey version 6.2.0. + * + * @see {@link https://valkey.io/commands/zunion/|valkey.io} for details. + * + * @param keys - The keys of the sorted sets. + * + * Command Response - The resulting array of union elements. + */ + public zunion(keys: string[]): T { + return this.addAndReturn(createZUnion(keys)); + } + + /** + * Computes the intersection of sorted sets given by the specified `keys` and returns a list of union elements with scores. + * To get the elements only, see `zunion`. + * + * @see {@link https://valkey.io/commands/zunion/|valkey.io} for details. + * + * since Valkey version 6.2.0. + * + * @param keys - The keys of the sorted sets with possible formats: + * - string[] - for keys only. + * - KeyWeight[] - for weighted keys with score multipliers. + * @param aggregationType - Specifies the aggregation strategy to apply when combining the scores of elements. See `AggregationType`. + * + * Command Response - The resulting sorted set with scores. + */ + public zunionWithScores( + keys: string[] | KeyWeight[], + aggregationType?: AggregationType, + ): T { + return this.addAndReturn(createZUnion(keys, aggregationType, true)); + } + /** * Returns a random member from the sorted set stored at `key`. * diff --git a/node/tests/GlideClusterClient.test.ts b/node/tests/GlideClusterClient.test.ts index 4304a5a988..75717dc7f4 100644 --- a/node/tests/GlideClusterClient.test.ts +++ b/node/tests/GlideClusterClient.test.ts @@ -371,8 +371,12 @@ describe("GlideClusterClient", () => { client.smove("abc", "zxy", "value"), client.renamenx("abc", "zxy"), client.sinter(["abc", "zxy", "lkn"]), + client.zinter(["abc", "zxy", "lkn"]), + client.zinterWithScores(["abc", "zxy", "lkn"]), client.sinterstore("abc", ["zxy", "lkn"]), client.zinterstore("abc", ["zxy", "lkn"]), + client.zunion(["abc", "zxy", "lkn"]), + client.zunionWithScores(["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 1a0e4d2866..5304bab2b0 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -4489,6 +4489,348 @@ export function runBaseTests(config: { config.timeout, ); + async function zinterBasicTest(client: BaseClient) { + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + + const membersScores1 = { one: 1.0, two: 2.0 }; + const membersScores2 = { one: 1.5, two: 2.5, three: 3.5 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + expect(await client.zadd(key2, membersScores2)).toEqual(3); + + const resultZinter = await client.zinter([key1, key2]); + const expectedZinter = ["one", "two"]; + expect(resultZinter).toEqual(expectedZinter); + } + + async function zinterWithScoresBasicTest(client: BaseClient) { + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + + const membersScores1 = { one: 1.0, two: 2.0 }; + const membersScores2 = { one: 1.5, two: 2.5, three: 3.5 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + expect(await client.zadd(key2, membersScores2)).toEqual(3); + + const resultZinterWithScores = await client.zinterWithScores([ + key1, + key2, + ]); + const expectedZinterWithScores = { + one: 2.5, + two: 4.5, + }; + expect(resultZinterWithScores).toEqual(expectedZinterWithScores); + } + + async function zinterWithScoresWithMaxAggregation(client: BaseClient) { + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + + const membersScores1 = { one: 1.0, two: 2.0 }; + const membersScores2 = { one: 1.5, two: 2.5, three: 3.5 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + expect(await client.zadd(key2, membersScores2)).toEqual(3); + + // Intersection results are aggregated by the MAX score of elements + const zinterWithScoresResults = await client.zinterWithScores( + [key1, key2], + "MAX", + ); + const expectedMapMax = { + one: 1.5, + two: 2.5, + }; + expect(zinterWithScoresResults).toEqual(expectedMapMax); + } + + async function zinterWithScoresWithMinAggregation(client: BaseClient) { + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + + const membersScores1 = { one: 1.0, two: 2.0 }; + const membersScores2 = { one: 1.5, two: 2.5, three: 3.5 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + expect(await client.zadd(key2, membersScores2)).toEqual(3); + + // Intersection results are aggregated by the MIN score of elements + const zinterWithScoresResults = await client.zinterWithScores( + [key1, key2], + "MIN", + ); + const expectedMapMin = { + one: 1.0, + two: 2.0, + }; + expect(zinterWithScoresResults).toEqual(expectedMapMin); + } + + async function zinterWithScoresWithSumAggregation(client: BaseClient) { + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + + const membersScores1 = { one: 1.0, two: 2.0 }; + const membersScores2 = { one: 1.5, two: 2.5, three: 3.5 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + expect(await client.zadd(key2, membersScores2)).toEqual(3); + + // Intersection results are aggregated by the SUM score of elements + const zinterWithScoresResults = await client.zinterWithScores( + [key1, key2], + "SUM", + ); + const expectedMapSum = { + one: 2.5, + two: 4.5, + }; + expect(zinterWithScoresResults).toEqual(expectedMapSum); + } + + async function zinterWithScoresWithWeightsAndAggregation( + client: BaseClient, + ) { + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + + const membersScores1 = { one: 1.0, two: 2.0 }; + const membersScores2 = { one: 1.5, two: 2.5, three: 3.5 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + expect(await client.zadd(key2, membersScores2)).toEqual(3); + + // Intersection results are aggregated by the SUM score of elements with weights + const zinterWithScoresResults = await client.zinterWithScores( + [ + [key1, 3], + [key2, 2], + ], + "SUM", + ); + const expectedMapSum = { + one: 6, + two: 11, + }; + expect(zinterWithScoresResults).toEqual(expectedMapSum); + } + + async function zinterEmptyCases(client: BaseClient) { + const key1 = "{testKey}:1-" + uuidv4(); + + // Non existing key zinter + expect( + await client.zinter([key1, "{testKey}-non_existing_key"]), + ).toEqual([]); + + // Non existing key zinterWithScores + expect( + await client.zinterWithScores([key1, "{testKey}-non_existing_key"]), + ).toEqual({}); + + // Empty list check zinter + await expect(client.zinter([])).rejects.toThrow(); + + // Empty list check zinterWithScores + await expect(client.zinterWithScores([])).rejects.toThrow(); + } + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zinter test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster: RedisCluster) => { + if (cluster.checkIfServerVersionLessThan("6.2.0")) return; + await zinterBasicTest(client); + await zinterWithScoresBasicTest(client); + await zinterWithScoresWithMaxAggregation(client); + await zinterWithScoresWithMinAggregation(client); + await zinterWithScoresWithSumAggregation(client); + await zinterWithScoresWithWeightsAndAggregation(client); + await zinterEmptyCases(client); + }, protocol); + }, + config.timeout, + ); + + async function zunionBasicTest(client: BaseClient) { + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + + const membersScores1 = { one: 1.0, two: 2.0 }; + const membersScores2 = { one: 1.5, two: 2.5, three: 3.5 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + expect(await client.zadd(key2, membersScores2)).toEqual(3); + + const resultZunion = await client.zunion([key1, key2]); + const expectedZunion = ["one", "two", "three"]; + + expect(resultZunion.sort()).toEqual(expectedZunion.sort()); + } + + async function zunionWithScoresBasicTest(client: BaseClient) { + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + + const membersScores1 = { one: 1.0, two: 2.0 }; + const membersScores2 = { one: 1.5, two: 2.5, three: 3.5 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + expect(await client.zadd(key2, membersScores2)).toEqual(3); + + const resultZunionWithScores = await client.zunionWithScores([ + key1, + key2, + ]); + const expectedZunionWithScores = { + one: 2.5, + two: 4.5, + three: 3.5, + }; + expect(resultZunionWithScores).toEqual(expectedZunionWithScores); + } + + async function zunionWithScoresWithMaxAggregation(client: BaseClient) { + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + + const membersScores1 = { one: 1.0, two: 2.0 }; + const membersScores2 = { one: 1.5, two: 2.5, three: 3.5 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + expect(await client.zadd(key2, membersScores2)).toEqual(3); + + // Union results are aggregated by the MAX score of elements + const zunionWithScoresResults = await client.zunionWithScores( + [key1, key2], + "MAX", + ); + const expectedMapMax = { + one: 1.5, + two: 2.5, + three: 3.5, + }; + expect(zunionWithScoresResults).toEqual(expectedMapMax); + } + + async function zunionWithScoresWithMinAggregation(client: BaseClient) { + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + + const membersScores1 = { one: 1.0, two: 2.0 }; + const membersScores2 = { one: 1.5, two: 2.5, three: 3.5 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + expect(await client.zadd(key2, membersScores2)).toEqual(3); + + // Union results are aggregated by the MIN score of elements + const zunionWithScoresResults = await client.zunionWithScores( + [key1, key2], + "MIN", + ); + const expectedMapMin = { + one: 1.0, + two: 2.0, + three: 3.5, + }; + expect(zunionWithScoresResults).toEqual(expectedMapMin); + } + + async function zunionWithScoresWithSumAggregation(client: BaseClient) { + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + + const membersScores1 = { one: 1.0, two: 2.0 }; + const membersScores2 = { one: 1.5, two: 2.5, three: 3.5 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + expect(await client.zadd(key2, membersScores2)).toEqual(3); + + // Union results are aggregated by the SUM score of elements + const zunionWithScoresResults = await client.zunionWithScores( + [key1, key2], + "SUM", + ); + const expectedMapSum = { + one: 2.5, + two: 4.5, + three: 3.5, + }; + expect(zunionWithScoresResults).toEqual(expectedMapSum); + } + + async function zunionWithScoresWithWeightsAndAggregation( + client: BaseClient, + ) { + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + + const membersScores1 = { one: 1.0, two: 2.0 }; + const membersScores2 = { one: 1.5, two: 2.5, three: 3.5 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + expect(await client.zadd(key2, membersScores2)).toEqual(3); + + // Union results are aggregated by the SUM score of elements with weights + const zunionWithScoresResults = await client.zunionWithScores( + [ + [key1, 3], + [key2, 2], + ], + "SUM", + ); + const expectedMapSum = { + one: 6, + two: 11, + three: 7, + }; + expect(zunionWithScoresResults).toEqual(expectedMapSum); + } + + async function zunionEmptyCases(client: BaseClient) { + const key1 = "{testKey}:1-" + uuidv4(); + + const membersScores1 = { one: 1.0, two: 2.0 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + + // Non existing key zunion + expect( + await client.zunion([key1, "{testKey}-non_existing_key"]), + ).toEqual(["one", "two"]); + + // Non existing key zunionWithScores + expect( + await client.zunionWithScores([key1, "{testKey}-non_existing_key"]), + ).toEqual(membersScores1); + + // Empty list check zunion + await expect(client.zunion([])).rejects.toThrow(); + + // Empty list check zunionWithScores + await expect(client.zunionWithScores([])).rejects.toThrow(); + } + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zunion test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster: RedisCluster) => { + if (cluster.checkIfServerVersionLessThan("6.2.0")) return; + await zunionBasicTest(client); + await zunionWithScoresBasicTest(client); + await zunionWithScoresWithMaxAggregation(client); + await zunionWithScoresWithMinAggregation(client); + await zunionWithScoresWithSumAggregation(client); + await zunionWithScoresWithWeightsAndAggregation(client); + await zunionEmptyCases(client); + }, protocol); + }, + config.timeout, + ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( `type test_%p`, async (protocol) => { diff --git a/node/tests/TestUtilities.ts b/node/tests/TestUtilities.ts index 2faf78335d..9c3b53cda4 100644 --- a/node/tests/TestUtilities.ts +++ b/node/tests/TestUtilities.ts @@ -634,6 +634,8 @@ export async function transactionTest( const key23 = "{key}" + uuidv4(); // zset random const key24 = "{key}" + uuidv4(); // list value const key25 = "{key}" + uuidv4(); // Geospatial Data/ZSET + const key26 = "{key}" + uuidv4(); // sorted set + const key27 = "{key}" + uuidv4(); // sorted set const field = uuidv4(); const value = uuidv4(); const groupName1 = uuidv4(); @@ -990,6 +992,28 @@ export async function transactionTest( responseData.push(['zmscore(key12, ["two", "one"]', [2.0, 1.0]]); baseTransaction.zinterstore(key12, [key12, key13]); responseData.push(["zinterstore(key12, [key12, key13])", 0]); + + if (gte(version, "6.2.0")) { + baseTransaction.zadd(key26, { one: 1, two: 2 }); + responseData.push(["zadd(key26, { one: 1, two: 2 })", 2]); + baseTransaction.zadd(key27, { one: 1, two: 2, three: 3.5 }); + responseData.push([ + "zadd(key27, { one: 1, two: 2, three: 3.5 })", + 3, + ]); + baseTransaction.zinter([key27, key26]); + responseData.push(["zinter([key27, key26])", ["one", "two"]]); + baseTransaction.zinterWithScores([key27, key26]); + responseData.push([ + "zinterWithScores([key27, key26])", + { one: 2, two: 4 }, + ]); + baseTransaction.zunionWithScores([key27, key26]); + responseData.push([ + "zunionWithScores([key27, key26])", + { one: 2, two: 4, three: 3.5 }, + ]); + } } else { baseTransaction.zinterstore(key12, [key12, key13]); responseData.push(["zinterstore(key12, [key12, key13])", 2]);