Skip to content

Commit

Permalink
Node: add ZDIFF command (#1972)
Browse files Browse the repository at this point in the history
* Node: add ZDIFF command

Signed-off-by: aaron-congo <aaron.congo@improving.com>
  • Loading branch information
aaron-congo authored Jul 18, 2024
1 parent 96a60f3 commit f19f4b1
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Node: Added LPUSHX and RPUSHX command([#1959](https://github.com/valkey-io/valkey-glide/pull/1959))
* Node: Added LSET command ([#1952](https://github.com/valkey-io/valkey-glide/pull/1952))
* Node: Added SDIFFSTORE command ([#1931](https://github.com/valkey-io/valkey-glide/pull/1931))
* Node: Added ZDIFF command ([#1972](https://github.com/valkey-io/valkey-glide/pull/1972))
* Node: Added SINTERCARD command ([#1956](https://github.com/valkey-io/valkey-glide/pull/1956))
* 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))
Expand Down
54 changes: 54 additions & 0 deletions node/src/BaseClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ import {
createZAdd,
createZCard,
createZCount,
createZDiff,
createZDiffWithScores,
createZInterCard,
createZInterstore,
createZPopMax,
Expand Down Expand Up @@ -2276,6 +2278,58 @@ export class BaseClient {
return this.createWritePromise(createZInterCard(keys, limit));
}

/**
* Returns the difference between the first sorted set and all the successive sorted sets.
* To get the elements with their scores, see {@link zdiffWithScores}.
*
* See https://valkey.io/commands/zdiff/ 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.
* @returns An `array` of elements representing the difference between the sorted sets.
* If the first key does not exist, it is treated as an empty sorted set, and the command returns an empty `array`.
*
* since Valkey version 6.2.0.
*
* @example
* ```typescript
* await client.zadd("zset1", {"member1": 1.0, "member2": 2.0, "member3": 3.0});
* await client.zadd("zset2", {"member2": 2.0});
* await client.zadd("zset3", {"member3": 3.0});
* const result = await client.zdiff(["zset1", "zset2", "zset3"]);
* console.log(result); // Output: ["member1"] - "member1" is in "zset1" but not "zset2" or "zset3".
* ```
*/
public zdiff(keys: string[]): Promise<string[]> {
return this.createWritePromise(createZDiff(keys));
}

/**
* Returns the difference between the first sorted set and all the successive sorted sets, with the associated
* scores.
*
* See https://valkey.io/commands/zdiff/ 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.
* @returns A map of elements and their scores representing the difference between the sorted sets.
* If the first key does not exist, it is treated as an empty sorted set, and the command returns an empty `array`.
*
* since Valkey version 6.2.0.
*
* @example
* ```typescript
* await client.zadd("zset1", {"member1": 1.0, "member2": 2.0, "member3": 3.0});
* await client.zadd("zset2", {"member2": 2.0});
* await client.zadd("zset3", {"member3": 3.0});
* const result = await client.zdiffWithScores(["zset1", "zset2", "zset3"]);
* console.log(result); // Output: {"member1": 1.0} - "member1" is in "zset1" but not "zset2" or "zset3".
* ```
*/
public zdiffWithScores(keys: string[]): Promise<Record<string, number>> {
return this.createWritePromise(createZDiffWithScores(keys));
}

/** Returns the score of `member` in the sorted set stored at `key`.
* See https://valkey.io/commands/zscore/ for more details.
*
Expand Down
19 changes: 19 additions & 0 deletions node/src/Commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1026,6 +1026,25 @@ export function createZInterCard(
return createCommand(RequestType.ZInterCard, args);
}

/**
* @internal
*/
export function createZDiff(keys: string[]): command_request.Command {
const args: string[] = keys;
args.unshift(keys.length.toString());
return createCommand(RequestType.ZDiff, args);
}

/**
* @internal
*/
export function createZDiffWithScores(keys: string[]): command_request.Command {
const args: string[] = keys;
args.unshift(keys.length.toString());
args.push("WITHSCORES");
return createCommand(RequestType.ZDiff, args);
}

/**
* @internal
*/
Expand Down
36 changes: 36 additions & 0 deletions node/src/Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ import {
createZAdd,
createZCard,
createZCount,
createZDiff,
createZDiffWithScores,
createZInterCard,
createZInterstore,
createZPopMax,
Expand Down Expand Up @@ -1150,6 +1152,40 @@ export class BaseTransaction<T extends BaseTransaction<T>> {
return this.addAndReturn(createZInterCard(keys, limit));
}

/**
* Returns the difference between the first sorted set and all the successive sorted sets.
* To get the elements with their scores, see {@link zdiffWithScores}.
*
* See https://valkey.io/commands/zdiff/ for more details.
*
* @param keys - The keys of the sorted sets.
*
* Command Response - An `array` of elements representing the difference between the sorted sets.
* If the first key does not exist, it is treated as an empty sorted set, and the command returns an empty `array`.
*
* since Valkey version 6.2.0.
*/
public zdiff(keys: string[]): T {
return this.addAndReturn(createZDiff(keys));
}

/**
* Returns the difference between the first sorted set and all the successive sorted sets, with the associated
* scores.
*
* See https://valkey.io/commands/zdiff/ for more details.
*
* @param keys - The keys of the sorted sets.
*
* Command Response - A map of elements and their scores representing the difference between the sorted sets.
* If the first key does not exist, it is treated as an empty sorted set, and the command returns an empty `array`.
*
* since Valkey version 6.2.0.
*/
public zdiffWithScores(keys: string[]): T {
return this.addAndReturn(createZDiffWithScores(keys));
}

/** Returns the score of `member` in the sorted set stored at `key`.
* See https://valkey.io/commands/zscore/ for more details.
*
Expand Down
2 changes: 2 additions & 0 deletions node/tests/RedisClusterClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,8 @@ describe("GlideClusterClient", () => {
client.sintercard(["abc", "zxy", "lkn"]),
client.sinterstore("abc", ["zxy", "lkn"]),
client.zinterstore("abc", ["zxy", "lkn"]),
client.zdiff(["abc", "zxy", "lkn"]),
client.zdiffWithScores(["abc", "zxy", "lkn"]),
client.sunionstore("abc", ["zxy", "lkn"]),
client.sunion(["abc", "zxy", "lkn"]),
client.pfcount(["abc", "zxy", "lkn"]),
Expand Down
70 changes: 70 additions & 0 deletions node/tests/SharedTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2171,6 +2171,76 @@ export function runBaseTests<Context>(config: {
config.timeout,
);

it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
`zdiff test_%p`,
async (protocol) => {
await runTest(async (client: BaseClient) => {
if (await checkIfServerVersionLessThan("6.2.0")) {
return;
}

const key1 = `{key}-${uuidv4()}`;
const key2 = `{key}-${uuidv4()}`;
const key3 = `{key}-${uuidv4()}`;
const nonExistingKey = `{key}-${uuidv4()}`;
const stringKey = `{key}-${uuidv4()}`;

const entries1 = {
one: 1.0,
two: 2.0,
three: 3.0,
};
const entries2 = { two: 2.0 };
const entries3 = {
one: 1.0,
two: 2.0,
three: 3.0,
four: 4.0,
};

expect(await client.zadd(key1, entries1)).toEqual(3);
expect(await client.zadd(key2, entries2)).toEqual(1);
expect(await client.zadd(key3, entries3)).toEqual(4);

checkSimple(await client.zdiff([key1, key2])).toEqual([
"one",
"three",
]);
checkSimple(await client.zdiff([key1, key3])).toEqual([]);
checkSimple(await client.zdiff([nonExistingKey, key3])).toEqual(
[],
);

let result = await client.zdiffWithScores([key1, key2]);
const expected = {
one: 1.0,
three: 3.0,
};
expect(compareMaps(result, expected)).toBe(true);

result = await client.zdiffWithScores([key1, key3]);
expect(compareMaps(result, {})).toBe(true);

result = await client.zdiffWithScores([nonExistingKey, key3]);
expect(compareMaps(result, {})).toBe(true);

// invalid arg - key list must not be empty
await expect(client.zdiff([])).rejects.toThrow(RequestError);
await expect(client.zdiffWithScores([])).rejects.toThrow(
RequestError,
);

// key exists, but it is not a sorted set
checkSimple(await client.set(stringKey, "foo")).toEqual("OK");
await expect(client.zdiff([stringKey, key1])).rejects.toThrow();
await expect(
client.zdiffWithScores([stringKey, key1]),
).rejects.toThrow();
}, protocol);
},
config.timeout,
);

it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
`zscore test_%p`,
async (protocol) => {
Expand Down
10 changes: 9 additions & 1 deletion node/tests/TestUtilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -491,8 +491,16 @@ export async function transactionTest(
args.push({ member2: 3, member3: 3.5, member4: 4, member5: 5 });
baseTransaction.zadd(key12, { one: 1, two: 2 });
args.push(2);
baseTransaction.zadd(key13, { one: 1, two: 2, tree: 3.5 });
baseTransaction.zadd(key13, { one: 1, two: 2, three: 3.5 });
args.push(3);

if (!(await checkIfServerVersionLessThan("6.2.0"))) {
baseTransaction.zdiff([key13, key12]);
args.push(["three"]);
baseTransaction.zdiffWithScores([key13, key12]);
args.push({ three: 3.5 });
}

baseTransaction.zinterstore(key12, [key12, key13]);
args.push(2);
baseTransaction.zcount(key8, { value: 2 }, "positiveInfinity");
Expand Down

0 comments on commit f19f4b1

Please sign in to comment.