diff --git a/CHANGELOG.md b/CHANGELOG.md index 563251e54d..00578bd121 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +#### Changes +* Node: Added SUNION command ([#1919](https://github.com/valkey-io/valkey-glide/pull/1919)) + +## 1.0.0 (2024-07-09) + #### Changes * Node: Added ZINTERSTORE command ([#1513](https://github.com/valkey-io/valkey-glide/pull/1513)) * Python: Added OBJECT ENCODING command ([#1471](https://github.com/valkey-io/valkey-glide/pull/1471)) @@ -94,7 +99,7 @@ #### Fixes * Python: fixing a bug with transaction exec ([#1796](https://github.com/valkey-io/valkey-glide/pull/1796)) -## 0.4.1 (2024-02-06) +## 0.4.1 (2024-06-02) #### Fixes * Node: Fix set command bug with expiry option ([#1508](https://github.com/valkey-io/valkey-glide/pull/1508)) diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts index 3ba5739d21..0b9663fb26 100644 --- a/node/src/BaseClient.ts +++ b/node/src/BaseClient.ts @@ -103,6 +103,7 @@ import { createZRemRangeByRank, createZRemRangeByScore, createZScore, + createSUnion, } from "./Commands"; import { ClosingError, @@ -1316,6 +1317,31 @@ export class BaseClient { ); } + /** + * Gets the union of all the given sets. + * + * See https://valkey.io/commands/sunion/ for more details. + * + * @remarks When in cluster mode, all `keys` must map to the same hash slot. + * @param keys - The keys of the sets. + * @returns A set of members which are present in at least one of the given sets. + * If none of the sets exist, an empty set will be returned. + * + * @example + * ```typescript + * await client.sadd("my_set1", ["member1", "member2"]); + * await client.sadd("my_set2", ["member2", "member3"]); + * const result1 = await client.sunion(["my_set1", "my_set2"]); + * console.log(result1); // Output: Set {'member1', 'member2', 'member3'} - Sets "my_set1" and "my_set2" have three unique members. + * + * const result2 = await client.sunion(["my_set1", "non_existing_set"]); + * console.log(result2); // Output: Set {'member1', 'member2'} + * ``` + */ + public sunion(keys: string[]): Promise> { + return this.createWritePromise(createSUnion(keys)); + } + /** * Stores the members of the union of all given sets specified by `keys` into a new set * at `destination`. diff --git a/node/src/Commands.ts b/node/src/Commands.ts index 12d3667589..cca74f5179 100644 --- a/node/src/Commands.ts +++ b/node/src/Commands.ts @@ -614,6 +614,13 @@ export function createSInter(keys: string[]): command_request.Command { return createCommand(RequestType.SInter, keys); } +/** + * @internal + */ +export function createSUnion(keys: string[]): command_request.Command { + return createCommand(RequestType.SUnion, keys); +} + /** * @internal */ diff --git a/node/src/Transaction.ts b/node/src/Transaction.ts index b535eb870c..160d0e3f5e 100644 --- a/node/src/Transaction.ts +++ b/node/src/Transaction.ts @@ -108,6 +108,7 @@ import { createZRemRangeByRank, createZRemRangeByScore, createZScore, + createSUnion, } from "./Commands"; import { command_request } from "./ProtobufMessage"; @@ -732,6 +733,20 @@ export class BaseTransaction> { return this.addAndReturn(createSInter(keys), true); } + /** + * Gets the union of all the given sets. + * + * See https://valkey.io/commands/sunion/ for more details. + * + * @param keys - The keys of the sets. + * + * Command Response - A set of members which are present in at least one of the given sets. + * If none of the sets exist, an empty set will be returned. + */ + public sunion(keys: string[]): T { + return this.addAndReturn(createSUnion(keys)); + } + /** * Stores the members of the union of all given sets specified by `keys` into a new set * at `destination`. diff --git a/node/tests/RedisClusterClient.test.ts b/node/tests/RedisClusterClient.test.ts index c627a8f9bd..dc51d2d7dc 100644 --- a/node/tests/RedisClusterClient.test.ts +++ b/node/tests/RedisClusterClient.test.ts @@ -302,6 +302,7 @@ describe("GlideClusterClient", () => { client.sinter(["abc", "zxy", "lkn"]), client.zinterstore("abc", ["zxy", "lkn"]), client.sunionstore("abc", ["zxy", "lkn"]), + client.sunion(["zxy", "lkn"]), client.pfcount(["abc", "zxy", "lkn"]), // TODO all rest multi-key commands except ones tested below ]; diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts index a14032bfa9..5c6a4b7dc9 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -1191,6 +1191,39 @@ export function runBaseTests(config: { config.timeout, ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `sunion test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key1 = `{key}:${uuidv4()}`; + const key2 = `{key}:${uuidv4()}`; + const stringKey = `{key}:${uuidv4()}`; + const nonExistingKey = `{key}:${uuidv4()}`; + const memberList1 = ["a", "b", "c"]; + const memberList2 = ["b", "c", "d", "e"]; + + expect(await client.sadd(key1, memberList1)).toEqual(3); + expect(await client.sadd(key2, memberList2)).toEqual(4); + checkSimple(await client.sunion([key1, key2])).toEqual( + new Set(["a", "b", "c", "d", "e"]), + ); + + // invalid argument - key list must not be empty + await expect(client.sunion([])).rejects.toThrow(); + + // non-existing key returns the set of existing keys + checkSimple( + await client.sunion([key1, nonExistingKey]), + ).toEqual(new Set(memberList1)); + + // key exists, but it is not a set + expect(await client.set(stringKey, "value")).toEqual("OK"); + await expect(client.sunion([stringKey])).rejects.toThrow(); + }, protocol); + }, + config.timeout, + ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( `sunionstore test_%p`, async (protocol) => { diff --git a/node/tests/TestUtilities.ts b/node/tests/TestUtilities.ts index 5a78091df0..554952b531 100644 --- a/node/tests/TestUtilities.ts +++ b/node/tests/TestUtilities.ts @@ -388,6 +388,8 @@ export async function transactionTest( args.push(2); baseTransaction.sunionstore(key7, [key7, key7]); args.push(2); + baseTransaction.sunion([key7, key7]); + args.push(new Set(["bar", "foo"])); baseTransaction.sinter([key7, key7]); args.push(new Set(["bar", "foo"])); baseTransaction.srem(key7, ["foo"]);