diff --git a/CHANGELOG.md b/CHANGELOG.md index 25e9b53eb8..5fe0fa89e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ #### Changes +* 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)) * Node: Added SDIFF command ([#1924](https://github.com/valkey-io/valkey-glide/pull/1924)) diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts index 543bf4de27..07ce579286 100644 --- a/node/src/BaseClient.ts +++ b/node/src/BaseClient.ts @@ -75,6 +75,7 @@ import { createSCard, createSDiff, createSInter, + createSInterStore, createSIsMember, createSMembers, createSMove, @@ -1318,6 +1319,26 @@ export class BaseClient { ); } + /** + * Stores the members of the intersection of all given sets specified by `keys` into a new set at `destination`. + * + * See https://valkey.io/commands/sinterstore/ for more details. + * + * @remarks When in cluster mode, `destination` and all `keys` must map to the same hash slot. + * @param destination - The key of the destination set. + * @param keys - The keys from which to retrieve the set members. + * @returns The number of elements in the resulting set. + * + * @example + * ```typescript + * const result = await client.sinterstore("my_set", ["set1", "set2"]); + * console.log(result); // Output: 2 - Two elements were stored at "my_set", and those elements are the intersection of "set1" and "set2". + * ``` + */ + public sinterstore(destination: string, keys: string[]): Promise { + return this.createWritePromise(createSInterStore(destination, keys)); + } + /** * Computes the difference between the first set and all the successive sets in `keys`. * diff --git a/node/src/Commands.ts b/node/src/Commands.ts index 29897a0ad8..97b324e2c4 100644 --- a/node/src/Commands.ts +++ b/node/src/Commands.ts @@ -614,6 +614,16 @@ export function createSInter(keys: string[]): command_request.Command { return createCommand(RequestType.SInter, keys); } +/** + * @internal + */ +export function createSInterStore( + destination: string, + keys: string[], +): command_request.Command { + return createCommand(RequestType.SInterStore, [destination].concat(keys)); +} + /** * @internal */ diff --git a/node/src/Transaction.ts b/node/src/Transaction.ts index ff42a96135..d67f6a0195 100644 --- a/node/src/Transaction.ts +++ b/node/src/Transaction.ts @@ -78,6 +78,7 @@ import { createSCard, createSDiff, createSInter, + createSInterStore, createSIsMember, createSMembers, createSMove, @@ -734,6 +735,20 @@ export class BaseTransaction> { return this.addAndReturn(createSInter(keys), true); } + /** + * Stores the members of the intersection of all given sets specified by `keys` into a new set at `destination`. + * + * See https://valkey.io/commands/sinterstore/ for more details. + * + * @param destination - The key of the destination set. + * @param keys - The keys from which to retrieve the set members. + * + * Command Response - The number of elements in the resulting set. + */ + public sinterstore(destination: string, keys: string[]): T { + return this.addAndReturn(createSInterStore(destination, keys)); + } + /** * Computes the difference between the first set and all the successive sets in `keys`. * diff --git a/node/tests/RedisClusterClient.test.ts b/node/tests/RedisClusterClient.test.ts index cee425de3b..6b48dbc9c7 100644 --- a/node/tests/RedisClusterClient.test.ts +++ b/node/tests/RedisClusterClient.test.ts @@ -300,6 +300,7 @@ describe("GlideClusterClient", () => { client.smove("abc", "zxy", "value"), client.renamenx("abc", "zxy"), client.sinter(["abc", "zxy", "lkn"]), + client.sinterstore("abc", ["zxy", "lkn"]), client.zinterstore("abc", ["zxy", "lkn"]), client.sunionstore("abc", ["zxy", "lkn"]), client.sunion(["abc", "zxy", "lkn"]), diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts index 6ff15091ee..8af80ab999 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -1191,6 +1191,64 @@ export function runBaseTests(config: { config.timeout, ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `sinterstore test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key1 = `{key}-1-${uuidv4()}`; + const key2 = `{key}-2-${uuidv4()}`; + const key3 = `{key}-3-${uuidv4()}`; + const nonExistingKey = `{key}-4-${uuidv4()}`; + const stringKey = `{key}-5-${uuidv4()}`; + const member1_list = ["a", "b", "c"]; + const member2_list = ["c", "d", "e"]; + + expect(await client.sadd(key1, member1_list)).toEqual(3); + expect(await client.sadd(key2, member2_list)).toEqual(3); + + // store in a new key + expect(await client.sinterstore(key3, [key1, key2])).toEqual(1); + checkSimple(await client.smembers(key3)).toEqual( + new Set(["c"]), + ); + + // overwrite existing set, which is also a source set + expect(await client.sinterstore(key2, [key2, key3])).toEqual(1); + checkSimple(await client.smembers(key2)).toEqual( + new Set(["c"]), + ); + + // source set is the same as the existing set + expect(await client.sinterstore(key2, [key2])).toEqual(1); + checkSimple(await client.smembers(key2)).toEqual( + new Set(["c"]), + ); + + // intersection with non-existing key + expect( + await client.sinterstore(key1, [key2, nonExistingKey]), + ).toEqual(0); + checkSimple(await client.smembers(key1)).toEqual(new Set()); + + // invalid argument - key list must not be empty + await expect(client.sinterstore(key3, [])).rejects.toThrow(); + + // non-set key + checkSimple(await client.set(stringKey, "foo")).toEqual("OK"); + await expect( + client.sinterstore(key3, [stringKey]), + ).rejects.toThrow(); + + // overwrite non-set key + expect(await client.sinterstore(stringKey, [key2])).toEqual(1); + checkSimple(await client.smembers(stringKey)).toEqual( + new Set("c"), + ); + }, protocol); + }, + config.timeout, + ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( `sdiff test_%p`, async (protocol) => { diff --git a/node/tests/TestUtilities.ts b/node/tests/TestUtilities.ts index d221b6bc74..b6d106a791 100644 --- a/node/tests/TestUtilities.ts +++ b/node/tests/TestUtilities.ts @@ -392,6 +392,8 @@ export async function transactionTest( args.push(new Set(["bar", "foo"])); baseTransaction.sinter([key7, key7]); args.push(new Set(["bar", "foo"])); + baseTransaction.sinterstore(key7, [key7, key7]); + args.push(2); baseTransaction.sdiff([key7, key7]); args.push(new Set()); baseTransaction.srem(key7, ["foo"]);