diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts index eee9ba9209..560ffb9daa 100644 --- a/node/src/BaseClient.ts +++ b/node/src/BaseClient.ts @@ -29,6 +29,10 @@ import { createMSet, createRPop, createRPush, + createSAdd, + createSCard, + createSMembers, + createSRem, createSet, } from "./Commands"; import { @@ -606,6 +610,53 @@ export class BaseClient { return this.createWritePromise(createRPop(key, count)); } + /** Adds the specified members to the set stored at `key`. Specified members that are already a member of this set are ignored. + * If `key` does not exist, a new set is created before adding `members`. + * See https://redis.io/commands/sadd/ for details. + * + * @param key - The key to store the members to its set. + * @param members - A list of members to add to the set stored at `key`. + * @returns the number of members that were added to the set, not including all the members already present in the set. + * If `key` holds a value that is not a set, an error is returned. + */ + public sadd(key: string, members: string[]): Promise { + return this.createWritePromise(createSAdd(key, members)); + } + + /** Removes the specified members from the set stored at `key`. Specified members that are not a member of this set are ignored. + * See https://redis.io/commands/srem/ for details. + * + * @param key - The key to remove the members from its set. + * @param members - A list of members to remove from the set stored at `key`. + * @returns the number of members that were removed from the set, not including non existing members. + * If `key` does not exist, it is treated as an empty set and this command returns 0. + * If `key` holds a value that is not a set, an error is returned. + */ + public srem(key: string, members: string[]): Promise { + return this.createWritePromise(createSRem(key, members)); + } + + /** Returns all the members of the set value stored at `key`. + * See https://redis.io/commands/smembers/ for details. + * + * @param key - The key to return its members. + * @returns all members of the set. If `key` holds a value that is not a set, an error is returned. + */ + public smembers(key: string): Promise { + return this.createWritePromise(createSMembers(key)); + } + + /** Returns the set cardinality (number of elements) of the set stored at `key`. + * See https://redis.io/commands/scard/ for details. + * + * @param key - The key to return the number of its members. + * @returns the cardinality (number of elements) of the set, or 0 if key does not exist. + * If `key` holds a value that is not a set, an error is returned. + */ + public scard(key: string): Promise { + return this.createWritePromise(createSCard(key)); + } + private readonly MAP_READ_FROM_REPLICA_STRATEGY: Record< ReadFromReplicaStrategy, connection_request.ReadFromReplicaStrategy diff --git a/node/src/Commands.ts b/node/src/Commands.ts index 7d48614a78..8d2ca592b6 100644 --- a/node/src/Commands.ts +++ b/node/src/Commands.ts @@ -373,6 +373,28 @@ export function createRPop(key: string, count?: number): redis_request.Command { return createCommand(RequestType.RPop, args); } +export function createSAdd( + key: string, + members: string[] +): redis_request.Command { + return createCommand(RequestType.SAdd, [key].concat(members)); +} + +export function createSRem( + key: string, + members: string[] +): redis_request.Command { + return createCommand(RequestType.SRem, [key].concat(members)); +} + +export function createSMembers(key: string): redis_request.Command { + return createCommand(RequestType.SMembers, [key]); +} + +export function createSCard(key: string): redis_request.Command { + return createCommand(RequestType.SCard, [key]); +} + export function createCustomCommand(commandName: string, args: string[]) { return createCommand(RequestType.CustomCommand, [commandName, ...args]); } diff --git a/node/src/Transaction.ts b/node/src/Transaction.ts index 65ff3b87ad..4c4cae0097 100644 --- a/node/src/Transaction.ts +++ b/node/src/Transaction.ts @@ -32,6 +32,10 @@ import { createPing, createRPop, createRPush, + createSAdd, + createSCard, + createSMembers, + createSRem, createSelect, createSet, } from "./Commands"; @@ -465,6 +469,57 @@ export class BaseTransaction { this.commands.push(createRPop(key, count)); } + /** Adds the specified members to the set stored at `key`. Specified members that are already a member of this set are ignored. + * If `key` does not exist, a new set is created before adding `members`. + * See https://redis.io/commands/sadd/ for details. + * + * @param key - The key to store the members to its set. + * @param members - A list of members to add to the set stored at `key`. + * + * Command Response - the number of members that were added to the set, not including all the members already present in the set. + * If `key` holds a value that is not a set, an error is returned. + */ + public sadd(key: string, members: string[]) { + this.commands.push(createSAdd(key, members)); + } + + /** Removes the specified members from the set stored at `key`. Specified members that are not a member of this set are ignored. + * See https://redis.io/commands/srem/ for details. + * + * @param key - The key to remove the members from its set. + * @param members - A list of members to remove from the set stored at `key`. + * + * Command Response - the number of members that were removed from the set, not including non existing members. + * If `key` does not exist, it is treated as an empty set and this command returns 0. + * If `key` holds a value that is not a set, an error is returned. + */ + public srem(key: string, members: string[]) { + this.commands.push(createSRem(key, members)); + } + + /** Returns all the members of the set value stored at `key`. + * See https://redis.io/commands/smembers/ for details. + * + * @param key - The key to return its members. + * + * Command Response - all members of the set. If `key` holds a value that is not a set, an error is returned. + */ + public smembers(key: string) { + this.commands.push(createSMembers(key)); + } + + /** Returns the set cardinality (number of elements) of the set stored at `key`. + * See https://redis.io/commands/scard/ for details. + * + * @param key - The key to return the number of its members. + * + * Command Response - the cardinality (number of elements) of the set, or 0 if key does not exist. + * If `key` holds a value that is not a set, an error is returned. + */ + public scard(key: string) { + this.commands.push(createSCard(key)); + } + /** Executes a single command, without checking inputs. Every part of the command, including subcommands, * should be added as a separate value in args. * diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts index 2aca496dd8..fa979e9d5e 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -48,6 +48,10 @@ type BaseClient = { lrange: (key: string, start: number, end: number) => Promise; rpush: (key: string, elements: string[]) => Promise; rpop: (key: string, count?: number) => Promise; + sadd: (key: string, members: string[]) => Promise; + srem: (key: string, members: string[]) => Promise; + smembers: (key: string) => Promise; + scard: (key: string) => Promise; customCommand: (commandName: string, args: string[]) => Promise; }; @@ -797,6 +801,84 @@ export function runBaseTests(config: { }, config.timeout ); + + it( + "sadd, srem, scard and smembers with existing key", + async () => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + const valueList = ["member1", "member2", "member3", "member4"]; + expect(await client.sadd(key, valueList)).toEqual(4); + expect( + await client.srem(key, ["member3", "nonExistingMember"]) + ).toEqual(1); + /// compare the 2 sets. + expect(await client.smembers(key)).toEqual( + expect.arrayContaining(["member1", "member2", "member4"]) + ); + expect(await client.srem(key, ["member1"])).toEqual(1); + expect(await client.scard(key)).toEqual(2); + }); + }, + config.timeout + ); + + it( + "srem, scard and smembers with non existing key", + async () => { + await runTest(async (client: BaseClient) => { + expect(await client.srem("nonExistingKey", ["member"])).toEqual( + 0 + ); + expect(await client.scard("nonExistingKey")).toEqual(0); + expect(await client.smembers("nonExistingKey")).toEqual([]); + }); + }, + config.timeout + ); + + it( + "sadd, srem, scard and smembers with with key that holds a value that is not a set", + async () => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + expect(await client.set(key, "foo")).toEqual("OK"); + + try { + expect(await client.sadd(key, ["bar"])).toThrow(); + } catch (e) { + expect((e as Error).message).toMatch( + "Operation against a key holding the wrong kind of value" + ); + } + + try { + expect(await client.srem(key, ["bar"])).toThrow(); + } catch (e) { + expect((e as Error).message).toMatch( + "Operation against a key holding the wrong kind of value" + ); + } + + try { + expect(await client.scard(key)).toThrow(); + } catch (e) { + expect((e as Error).message).toMatch( + "Operation against a key holding the wrong kind of value" + ); + } + + try { + expect(await client.smembers(key)).toThrow(); + } catch (e) { + expect((e as Error).message).toMatch( + "Operation against a key holding the wrong kind of value" + ); + } + }); + }, + config.timeout + ); } export function runCommonTests(config: { diff --git a/node/tests/TestUtilities.ts b/node/tests/TestUtilities.ts index 73f7dfaf50..ac9604a76d 100644 --- a/node/tests/TestUtilities.ts +++ b/node/tests/TestUtilities.ts @@ -55,6 +55,7 @@ export function transactionTest( const key4 = "{key}" + uuidv4(); const key5 = "{key}" + uuidv4(); const key6 = "{key}" + uuidv4(); + const key7 = "{key}" + uuidv4(); const field = uuidv4(); const value = uuidv4(); baseTransaction.set(key1, "bar"); @@ -76,6 +77,10 @@ export function transactionTest( baseTransaction.lpop(key5); baseTransaction.rpush(key6, [field + "1", field + "2"]); baseTransaction.rpop(key6); + baseTransaction.sadd(key7, ["bar", "foo"]); + baseTransaction.srem(key7, ["foo"]); + baseTransaction.scard(key7); + baseTransaction.smembers(key7); return [ "OK", null, @@ -93,5 +98,9 @@ export function transactionTest( field + "2", 2, field + "2", + 2, + 1, + 1, + ["bar"], ]; }