Skip to content

Commit

Permalink
Add command ZRandMember (valkey-io#2013)
Browse files Browse the repository at this point in the history
  • Loading branch information
tjzhang-BQ authored Jul 29, 2024
1 parent f69904e commit e3dd23d
Show file tree
Hide file tree
Showing 6 changed files with 304 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@
* Node: Added GeoDist command ([#1988](https://github.com/valkey-io/valkey-glide/pull/1988))
* Node: Added GeoHash command ([#1997](https://github.com/valkey-io/valkey-glide/pull/1997))
* Node: Added HStrlen command ([#2020](https://github.com/valkey-io/valkey-glide/pull/2020))
* Node: Added ZRandMember command ([#2013](https://github.com/valkey-io/valkey-glide/pull/2013))

#### Breaking Changes
* Node: Update XREAD to return a Map of Map ([#1494](https://github.com/valkey-io/valkey-glide/pull/1494))
Expand Down
91 changes: 90 additions & 1 deletion node/src/BaseClient.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/**
* Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0
*/

import {
DEFAULT_TIMEOUT_IN_MILLISECONDS,
Script,
Expand Down Expand Up @@ -144,6 +143,7 @@ import {
createZMScore,
createZPopMax,
createZPopMin,
createZRandMember,
createZRange,
createZRangeWithScores,
createZRank,
Expand Down Expand Up @@ -193,6 +193,7 @@ export type ReturnType =
| null
| boolean
| bigint
| Buffer
| Set<ReturnType>
| ReturnTypeMap
| ReturnTypeAttribute
Expand Down Expand Up @@ -2840,6 +2841,94 @@ export class BaseClient {
);
}

/**
* Returns a random member from the sorted set stored at `key`.
*
* See https://valkey.io/commands/zrandmember/ for more details.
*
* @param keys - The key of the sorted set.
* @returns A string representing a random member from the sorted set.
* If the sorted set does not exist or is empty, the response will be `null`.
*
* @example
* ```typescript
* const payload1 = await client.zrandmember("mySortedSet");
* console.log(payload1); // Output: "Glide" (a random member from the set)
* ```
*
* @example
* ```typescript
* const payload2 = await client.zrandmember("nonExistingSortedSet");
* console.log(payload2); // Output: null since the sorted set does not exist.
* ```
*/
public async zrandmember(key: string): Promise<string | null> {
return this.createWritePromise(createZRandMember(key));
}

/**
* Returns random members from the sorted set stored at `key`.
*
* See https://valkey.io/commands/zrandmember/ for more details.
*
* @param keys - The key of the sorted set.
* @param count - The number of members to return.
* If `count` is positive, returns unique members.
* If negative, allows for duplicates.
* @returns An `array` of members from the sorted set.
* If the sorted set does not exist or is empty, the response will be an empty `array`.
*
* @example
* ```typescript
* const payload1 = await client.zrandmemberWithCount("mySortedSet", -3);
* console.log(payload1); // Output: ["Glide", "GLIDE", "node"]
* ```
*
* @example
* ```typescript
* const payload2 = await client.zrandmemberWithCount("nonExistingKey", 3);
* console.log(payload1); // Output: [] since the sorted set does not exist.
* ```
*/
public async zrandmemberWithCount(
key: string,
count: number,
): Promise<string[]> {
return this.createWritePromise(createZRandMember(key, count));
}

/**
* Returns random members with scores from the sorted set stored at `key`.
*
* See https://valkey.io/commands/zrandmember/ for more details.
*
* @param keys - The key of the sorted set.
* @param count - The number of members to return.
* If `count` is positive, returns unique members.
* If negative, allows for duplicates.
* @returns A 2D `array` of `[member, score]` `arrays`, where
* member is a `string` and score is a `number`.
* If the sorted set does not exist or is empty, the response will be an empty `array`.
*
* @example
* ```typescript
* const payload1 = await client.zrandmemberWithCountWithScore("mySortedSet", -3);
* console.log(payload1); // Output: [["Glide", 1.0], ["GLIDE", 1.0], ["node", 2.0]]
* ```
*
* @example
* ```typescript
* const payload2 = await client.zrandmemberWithCountWithScore("nonExistingKey", 3);
* console.log(payload1); // Output: [] since the sorted set does not exist.
* ```
*/
public async zrandmemberWithCountWithScores(
key: string,
count: number,
): Promise<[string, number][]> {
return this.createWritePromise(createZRandMember(key, count, true));
}

/** Returns the length of the string value stored at `key`.
* See https://valkey.io/commands/strlen/ for more details.
*
Expand Down
21 changes: 21 additions & 0 deletions node/src/Commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2406,3 +2406,24 @@ export function createHStrlen(
): command_request.Command {
return createCommand(RequestType.HStrlen, [key, field]);
}

/**
* @internal
*/
export function createZRandMember(
key: string,
count?: number,
withscores?: boolean,
): command_request.Command {
const args = [key];

if (count !== undefined) {
args.push(count.toString());
}

if (withscores) {
args.push("WITHSCORES");
}

return createCommand(RequestType.ZRandMember, args);
}
47 changes: 47 additions & 0 deletions node/src/Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ import {
createZMScore,
createZPopMax,
createZPopMin,
createZRandMember,
createZRange,
createZRangeWithScores,
createZRank,
Expand Down Expand Up @@ -1523,6 +1524,52 @@ export class BaseTransaction<T extends BaseTransaction<T>> {
);
}

/**
* Returns a random member from the sorted set stored at `key`.
*
* See https://valkey.io/commands/zrandmember/ for more details.
*
* @param keys - The key of the sorted set.
* Command Response - A string representing a random member from the sorted set.
* If the sorted set does not exist or is empty, the response will be `null`.
*/
public zrandmember(key: string): T {
return this.addAndReturn(createZRandMember(key));
}

/**
* Returns random members from the sorted set stored at `key`.
*
* See https://valkey.io/commands/zrandmember/ for more details.
*
* @param keys - The key of the sorted set.
* @param count - The number of members to return.
* If `count` is positive, returns unique members.
* If negative, allows for duplicates.
* Command Response - An `array` of members from the sorted set.
* If the sorted set does not exist or is empty, the response will be an empty `array`.
*/
public zrandmemberWithCount(key: string, count: number): T {
return this.addAndReturn(createZRandMember(key, count));
}

/**
* Returns random members with scores from the sorted set stored at `key`.
*
* See https://valkey.io/commands/zrandmember/ for more details.
*
* @param keys - The key of the sorted set.
* @param count - The number of members to return.
* If `count` is positive, returns unique members.
* If negative, allows for duplicates.
* Command Response - A 2D `array` of `[member, score]` `arrays`, where
* member is a `string` and score is a `number`.
* If the sorted set does not exist or is empty, the response will be an empty `array`.
*/
public zrandmemberWithCountWithScores(key: string, count: number): T {
return this.addAndReturn(createZRandMember(key, count, true));
}

/** Returns the string representation of the type of the value stored at `key`.
* See https://valkey.io/commands/type/ for more details.
*
Expand Down
133 changes: 133 additions & 0 deletions node/tests/SharedTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5446,6 +5446,139 @@ export function runBaseTests<Context>(config: {
},
config.timeout,
);

it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
`zrandmember test_%p`,
async (protocol) => {
await runTest(async (client: BaseClient) => {
const key1 = uuidv4();
const key2 = uuidv4();

const memberScores = { one: 1.0, two: 2.0 };
const elements = ["one", "two"];
expect(await client.zadd(key1, memberScores)).toBe(2);

// check random memember belongs to the set
const randmember = await client.zrandmember(key1);

if (randmember !== null) {
checkSimple(randmember in elements).toEqual(true);
}

// non existing key should return null
expect(await client.zrandmember("nonExistingKey")).toBeNull();

// Key exists, but is not a set
expect(await client.set(key2, "foo")).toBe("OK");
await expect(client.zrandmember(key2)).rejects.toThrow();
}, protocol);
},
config.timeout,
);

it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
`zrandmemberWithCount test_%p`,
async (protocol) => {
await runTest(async (client: BaseClient) => {
const key1 = uuidv4();
const key2 = uuidv4();

const memberScores = { one: 1.0, two: 2.0 };
expect(await client.zadd(key1, memberScores)).toBe(2);

// unique values are expected as count is positive
let randMembers = await client.zrandmemberWithCount(key1, 4);
expect(randMembers.length).toBe(2);
expect(randMembers.length).toEqual(new Set(randMembers).size);

// Duplicate values are expected as count is negative
randMembers = await client.zrandmemberWithCount(key1, -4);
expect(randMembers.length).toBe(4);
const randMemberSet = new Set<string>();

for (const member of randMembers) {
const memberStr = member + "";

if (!randMemberSet.has(memberStr)) {
randMemberSet.add(memberStr);
}
}

expect(randMembers.length).not.toEqual(randMemberSet.size);

// non existing key should return empty array
randMembers = await client.zrandmemberWithCount(
"nonExistingKey",
-4,
);
expect(randMembers.length).toBe(0);

// Key exists, but is not a set
expect(await client.set(key2, "foo")).toBe("OK");
await expect(
client.zrandmemberWithCount(key2, 1),
).rejects.toThrow();
}, protocol);
},
config.timeout,
);

it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
`zrandmemberWithCountWithScores test_%p`,
async (protocol) => {
await runTest(async (client: BaseClient) => {
const key1 = uuidv4();
const key2 = uuidv4();

const memberScores = { one: 1.0, two: 2.0 };
const memberScoreMap = new Map<string, number>([
["one", 1.0],
["two", 2.0],
]);
expect(await client.zadd(key1, memberScores)).toBe(2);

// unique values are expected as count is positive
let randMembers = await client.zrandmemberWithCountWithScores(
key1,
4,
);

for (const member of randMembers) {
const key = String(member[0]);
const score = Number(member[1]);
expect(score).toEqual(memberScoreMap.get(key));
}

// Duplicate values are expected as count is negative
randMembers = await client.zrandmemberWithCountWithScores(
key1,
-4,
);
expect(randMembers.length).toBe(4);
const keys = [];

for (const member of randMembers) {
keys.push(String(member[0]));
}

expect(randMembers.length).not.toEqual(new Set(keys).size);

// non existing key should return empty array
randMembers = await client.zrandmemberWithCountWithScores(
"nonExistingKey",
-4,
);
expect(randMembers.length).toBe(0);

// Key exists, but is not a set
expect(await client.set(key2, "foo")).toBe("OK");
await expect(
client.zrandmemberWithCount(key2, 1),
).rejects.toThrow();
}, protocol);
},
config.timeout,
);
}

export function runCommonTests<Context>(config: {
Expand Down
12 changes: 12 additions & 0 deletions node/tests/TestUtilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,7 @@ export async function transactionTest(
const key18 = "{key}" + uuidv4(); // Geospatial Data/ZSET
const key19 = "{key}" + uuidv4(); // bitmap
const key20 = "{key}" + uuidv4(); // list
const key21 = "{key}" + uuidv4(); // zset random
const field = uuidv4();
const value = uuidv4();
// array of tuples - first element is test name/description, second - expected return value
Expand Down Expand Up @@ -862,6 +863,17 @@ export async function transactionTest(
'geohash(key18, ["Palermo", "Catania", "NonExisting"])',
["sqc8b49rny0", "sqdtr74hyu0", null],
]);
baseTransaction.zadd(key21, { one: 1.0 });
responseData.push(["zadd(key21, {one: 1.0}", 1]);
baseTransaction.zrandmember(key21);
responseData.push(["zrandmember(key21)", "one"]);
baseTransaction.zrandmemberWithCount(key21, 1);
responseData.push(["zrandmemberWithCountWithScores(key21, 1)", "one"]);
baseTransaction.zrandmemberWithCountWithScores(key21, 1);
responseData.push([
"zrandmemberWithCountWithScores(key21, 1)",
[Buffer.from("one"), 1.0],
]);

if (gte("6.2.0", version)) {
baseTransaction
Expand Down

0 comments on commit e3dd23d

Please sign in to comment.