From 25246b7602b07c9487d58fb124543e310367656a Mon Sep 17 00:00:00 2001 From: Aaron <69273634+aaron-congo@users.noreply.github.com> Date: Wed, 12 Jun 2024 15:58:51 -0700 Subject: [PATCH] Node: add ZINTERCARD command (#1553) * Node: add ZINTERCARD command * PR suggestions --- CHANGELOG.md | 1 + node/src/BaseClient.ts | 24 +++++++++++++++ node/src/Commands.ts | 17 +++++++++++ node/src/Transaction.ts | 18 +++++++++++ node/tests/RedisClusterClient.test.ts | 11 +++++-- node/tests/SharedTests.ts | 43 +++++++++++++++++++++++++++ node/tests/TestUtilities.ts | 13 +++++++- 7 files changed, 124 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eee8c8f28f..b5ee9fe4f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ * Node: Added OBJECT FREQ command ([#1542](https://github.com/aws/glide-for-redis/pull/1542)) * Node: Added LINSERT command ([#1544](https://github.com/aws/glide-for-redis/pull/1544)) * Node: Added XLEN command ([#1555](https://github.com/aws/glide-for-redis/pull/1555)) +* Node: Added ZINTERCARD command ([#1553](https://github.com/aws/glide-for-redis/pull/1553)) ### Breaking Changes * Node: Update XREAD to return a Map of Map ([#1494](https://github.com/aws/glide-for-redis/pull/1494)) diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts index 12e24eced5..b849ac02ff 100644 --- a/node/src/BaseClient.ts +++ b/node/src/BaseClient.ts @@ -100,6 +100,7 @@ import { createZScore, createSUnionStore, createXLen, + createZInterCard, } from "./Commands"; import { ClosingError, @@ -1762,6 +1763,29 @@ export class BaseClient { return this.createWritePromise(createZCard(key)); } + /** + * Returns the cardinality of the intersection of the sorted sets specified by `keys`. + * + * See https://valkey.io/commands/zintercard/ 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 to intersect. + * @param limit - An optional argument that can be used to specify a maximum number for the + * intersection cardinality. If limit is not supplied, or if it is set to `0`, there will be no limit. + * @returns The cardinality of the intersection of the given sorted sets. + * + * since - Redis version 7.0.0. + * + * @example + * ```typescript + * const cardinality = await client.zintercard(["key1", "key2"], 10); + * console.log(cardinality); // Output: 3 - The intersection of the sorted sets at "key1" and "key2" has a cardinality of 3. + * ``` + */ + public zintercard(keys: string[], limit?: number): Promise { + return this.createWritePromise(createZInterCard(keys, limit)); + } + /** Returns the score of `member` in the sorted set stored at `key`. * See https://redis.io/commands/zscore/ for more details. * diff --git a/node/src/Commands.ts b/node/src/Commands.ts index a8ecdbf8e0..7be993ab62 100644 --- a/node/src/Commands.ts +++ b/node/src/Commands.ts @@ -898,6 +898,23 @@ export function createZCard(key: string): redis_request.Command { return createCommand(RequestType.ZCard, [key]); } +/** + * @internal + */ +export function createZInterCard( + keys: string[], + limit?: number, +): redis_request.Command { + let args: string[] = keys; + args.unshift(keys.length.toString()); + + if (limit != undefined) { + args = args.concat(["LIMIT", limit.toString()]); + } + + return createCommand(RequestType.ZInterCard, args); +} + /** * @internal */ diff --git a/node/src/Transaction.ts b/node/src/Transaction.ts index f2f6bb1e03..a496b15879 100644 --- a/node/src/Transaction.ts +++ b/node/src/Transaction.ts @@ -105,6 +105,7 @@ import { createZScore, createSUnionStore, createXLen, + createZInterCard, } from "./Commands"; import { redis_request } from "./ProtobufMessage"; @@ -979,6 +980,23 @@ export class BaseTransaction> { return this.addAndReturn(createZCard(key)); } + /** + * Returns the cardinality of the intersection of the sorted sets specified by `keys`. + * + * See https://valkey.io/commands/zintercard/ for more details. + * + * @param keys - The keys of the sorted sets to intersect. + * @param limit - An optional argument that can be used to specify a maximum number for the + * intersection cardinality. If limit is not supplied, or if it is set to `0`, there will be no limit. + * + * Command Response - The cardinality of the intersection of the given sorted sets. + * + * since - Redis version 7.0.0. + */ + public zintercard(keys: string[], limit?: number): T { + return this.addAndReturn(createZInterCard(keys, limit)); + } + /** Returns the score of `member` in the sorted set stored at `key`. * See https://redis.io/commands/zscore/ for more details. * diff --git a/node/tests/RedisClusterClient.test.ts b/node/tests/RedisClusterClient.test.ts index 7794aa70d8..77589c9cf2 100644 --- a/node/tests/RedisClusterClient.test.ts +++ b/node/tests/RedisClusterClient.test.ts @@ -19,7 +19,7 @@ import { RedisClusterClient, } from ".."; import { RedisCluster } from "../../utils/TestUtils.js"; -import { runBaseTests } from "./SharedTests"; +import { checkIfServerVersionLessThan, runBaseTests } from "./SharedTests"; import { flushAndCloseClient, getClientConfigurationOption, @@ -281,7 +281,10 @@ describe("RedisClusterClient", () => { getClientConfigurationOption(cluster.getAddresses(), protocol), ); - const promises = [ + const versionLessThan7 = + await checkIfServerVersionLessThan("7.0.0"); + + const promises: Promise[] = [ client.blpop(["abc", "zxy", "lkn"], 0.1), client.rename("abc", "zxy"), client.brpop(["abc", "zxy", "lkn"], 0.1), @@ -294,6 +297,10 @@ describe("RedisClusterClient", () => { // TODO all rest multi-key commands except ones tested below ]; + if (!versionLessThan7) { + promises.push(client.zintercard(["abc", "zxy", "lkn"])); + } + for (const promise of promises) { try { await promise; diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts index 89ec2054b5..823ebe4ad3 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -1636,6 +1636,49 @@ export function runBaseTests(config: { config.timeout, ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zintercard test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + if (await checkIfServerVersionLessThan("7.0.0")) { + return; + } + + const key1 = `{key}:${uuidv4()}`; + const key2 = `{key}:${uuidv4()}`; + const stringKey = `{key}:${uuidv4()}`; + const nonExistingKey = `{key}:${uuidv4()}`; + const memberScores1 = { one: 1, two: 2, three: 3 }; + const memberScores2 = { two: 2, three: 3, four: 4 }; + + expect(await client.zadd(key1, memberScores1)).toEqual(3); + expect(await client.zadd(key2, memberScores2)).toEqual(3); + + expect(await client.zintercard([key1, key2])).toEqual(2); + expect(await client.zintercard([key1, nonExistingKey])).toEqual( + 0, + ); + + expect(await client.zintercard([key1, key2], 0)).toEqual(2); + expect(await client.zintercard([key1, key2], 1)).toEqual(1); + expect(await client.zintercard([key1, key2], 2)).toEqual(2); + + // invalid argument - key list must not be empty + await expect(client.zintercard([])).rejects.toThrow(); + + // invalid argument - limit must be non-negative + await expect( + client.zintercard([key1, key2], -1), + ).rejects.toThrow(); + + // key exists, but it is not a sorted set + expect(await client.set(stringKey, "foo")).toEqual("OK"); + await expect(client.zintercard([stringKey])).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 b2f4cda3a2..c063c00a6b 100644 --- a/node/tests/TestUtilities.ts +++ b/node/tests/TestUtilities.ts @@ -232,6 +232,7 @@ export async function transactionTest( const key11 = "{key}" + uuidv4(); // hyper log log const key12 = "{key}" + uuidv4(); const key13 = "{key}" + uuidv4(); + const key14 = "{key}" + uuidv4(); // sorted set const field = uuidv4(); const value = uuidv4(); const args: ReturnType[] = []; @@ -380,7 +381,17 @@ export async function transactionTest( "negativeInfinity", "positiveInfinity", ); - args.push(1); + args.push(1); // key8 is now empty + + if (!(await checkIfServerVersionLessThan("7.0.0"))) { + baseTransaction.zadd(key14, { one: 1.0, two: 2.0 }); + args.push(2); + baseTransaction.zintercard([key8, key14]); + args.push(0); + baseTransaction.zintercard([key8, key14], 1); + args.push(0); + } + baseTransaction.xadd(key9, [["field", "value1"]], { id: "0-1" }); args.push("0-1"); baseTransaction.xadd(key9, [["field", "value2"]], { id: "0-2" });