Skip to content

Commit

Permalink
Node: add ZDIFFSTORE command (valkey-io#1985)
Browse files Browse the repository at this point in the history
* Node: add ZDIFFSTORE command

Signed-off-by: aaron-congo <[email protected]>
  • Loading branch information
aaron-congo authored Jul 19, 2024
1 parent 815e24c commit 37899df
Show file tree
Hide file tree
Showing 7 changed files with 144 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* 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 ZDIFFSTORE command ([#1985](https://github.com/valkey-io/valkey-glide/pull/1985))
* 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
30 changes: 30 additions & 0 deletions node/src/BaseClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ import {
createZCard,
createZCount,
createZDiff,
createZDiffStore,
createZDiffWithScores,
createZInterCard,
createZInterstore,
Expand Down Expand Up @@ -2378,6 +2379,35 @@ export class BaseClient {
return this.createWritePromise(createZDiffWithScores(keys));
}

/**
* Calculates the difference between the first sorted set and all the successive sorted sets in `keys` and stores
* the difference as a sorted set to `destination`, overwriting it if it already exists. Non-existent keys are
* treated as empty sets.
*
* See https://valkey.io/commands/zdiffstore/ for more details.
*
* @remarks When in cluster mode, all keys in `keys` and `destination` must map to the same hash slot.
* @param destination - The key for the resulting sorted set.
* @param keys - The keys of the sorted sets to compare.
* @returns The number of members in the resulting sorted set stored at `destination`.
*
* since Valkey version 6.2.0.
*
* @example
* ```typescript
* await client.zadd("zset1", {"member1": 1.0, "member2": 2.0});
* await client.zadd("zset2", {"member1": 1.0});
* const result1 = await client.zdiffstore("zset3", ["zset1", "zset2"]);
* console.log(result1); // Output: 1 - One member exists in "key1" but not "key2", and this member was stored in "zset3".
*
* const result2 = await client.zrange("zset3", {start: 0, stop: -1});
* console.log(result2); // Output: ["member2"] - "member2" is now stored in "my_sorted_set".
* ```
*/
public zdiffstore(destination: string, keys: string[]): Promise<number> {
return this.createWritePromise(createZDiffStore(destination, keys));
}

/** Returns the score of `member` in the sorted set stored at `key`.
* See https://valkey.io/commands/zscore/ for more details.
*
Expand Down
11 changes: 11 additions & 0 deletions node/src/Commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1069,6 +1069,17 @@ export function createZDiffWithScores(keys: string[]): command_request.Command {
return createCommand(RequestType.ZDiff, args);
}

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

/**
* @internal
*/
Expand Down
19 changes: 19 additions & 0 deletions node/src/Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ import {
createZCard,
createZCount,
createZDiff,
createZDiffStore,
createZDiffWithScores,
createZInterCard,
createZInterstore,
Expand Down Expand Up @@ -1223,6 +1224,24 @@ export class BaseTransaction<T extends BaseTransaction<T>> {
return this.addAndReturn(createZDiffWithScores(keys));
}

/**
* Calculates the difference between the first sorted set and all the successive sorted sets in `keys` and stores
* the difference as a sorted set to `destination`, overwriting it if it already exists. Non-existent keys are
* treated as empty sets.
*
* See https://valkey.io/commands/zdiffstore/ for more details.
*
* @param destination - The key for the resulting sorted set.
* @param keys - The keys of the sorted sets to compare.
*
* Command Response - The number of members in the resulting sorted set stored at `destination`.
*
* since Valkey version 6.2.0.
*/
public zdiffstore(destination: string, keys: string[]): T {
return this.addAndReturn(createZDiffStore(destination, keys));
}

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

it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
`zdiffstore 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 key4 = `{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);

expect(await client.zdiffstore(key4, [key1, key2])).toEqual(2);
const result1 = await client.zrangeWithScores(key4, {
start: 0,
stop: -1,
});
const expected1 = { one: 1.0, three: 3.0 };
expect(compareMaps(result1, expected1)).toBe(true);

expect(
await client.zdiffstore(key4, [key3, key2, key1]),
).toEqual(1);
const result2 = await client.zrangeWithScores(key4, {
start: 0,
stop: -1,
});
expect(compareMaps(result2, { four: 4.0 })).toBe(true);

expect(await client.zdiffstore(key4, [key1, key3])).toEqual(0);
const result3 = await client.zrangeWithScores(key4, {
start: 0,
stop: -1,
});
expect(compareMaps(result3, {})).toBe(true);

expect(
await client.zdiffstore(key4, [nonExistingKey, key1]),
).toEqual(0);
const result4 = await client.zrangeWithScores(key4, {
start: 0,
stop: -1,
});
expect(compareMaps(result4, {})).toBe(true);

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

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

it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
`zscore test_%p`,
async (protocol) => {
Expand Down
2 changes: 2 additions & 0 deletions node/tests/TestUtilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,8 @@ export async function transactionTest(
args.push(["three"]);
baseTransaction.zdiffWithScores([key13, key12]);
args.push({ three: 3.5 });
baseTransaction.zdiffstore(key13, [key13, key13]);
args.push(0);
}

baseTransaction.zinterstore(key12, [key12, key13]);
Expand Down

0 comments on commit 37899df

Please sign in to comment.