Skip to content

Commit

Permalink
Node: Add HRANDFIELD command. (valkey-io#2096)
Browse files Browse the repository at this point in the history
* Add `HRANDFIELD` command.

Signed-off-by: Yury-Fridlyand <yury.fridlyand@improving.com>
Signed-off-by: Andrew Carbonetto <andrew.carbonetto@improving.com>
Co-authored-by: Andrew Carbonetto <andrew.carbonetto@improving.com>
  • Loading branch information
2 people authored and cyip10 committed Aug 12, 2024
1 parent b157eaf commit 93f28fe
Show file tree
Hide file tree
Showing 8 changed files with 215 additions and 26 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#### Changes
* Node: Added HRANDFIELD command ([#2096](https://github.com/valkey-io/valkey-glide/pull/2096))
* Node: Added FUNCTION STATS commands ([#2082](https://github.com/valkey-io/valkey-glide/pull/2082))
* Node: Added XCLAIM command ([#2092](https://github.com/valkey-io/valkey-glide/pull/2092))
* Node: Added EXPIRETIME and PEXPIRETIME commands ([#2063](https://github.com/valkey-io/valkey-glide/pull/2063))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,7 @@ public interface HashBaseCommands {
*/
CompletableFuture<String[][]> hrandfieldWithCountWithValues(String key, long count);

/*
/**
* Retrieves up to <code>count</code> random field names along with their values from the hash
* value stored at <code>key</code>.
*
Expand Down
2 changes: 1 addition & 1 deletion java/integTest/src/test/java/glide/SharedCommandTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -1438,7 +1438,7 @@ public void hrandfield(BaseClient client) {
assertEquals(data.get(pair[0]), pair[1]);
}

// Key exists, but it is not a List
// Key exists, but it is not a hash
assertEquals(OK, client.set(key2, "value").get());
Exception executionException =
assertThrows(ExecutionException.class, () -> client.hrandfield(key2).get());
Expand Down
80 changes: 76 additions & 4 deletions node/src/BaseClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ import {
createHIncrByFloat,
createHLen,
createHMGet,
createHRandField,
createHScan,
createHSet,
createHSetNX,
Expand Down Expand Up @@ -1485,10 +1486,6 @@ export class BaseClient {
return this.createWritePromise(createHMGet(key, fields));
}

/**
*
*/

/** Returns if `field` is an existing field in the hash stored at `key`.
* See https://valkey.io/commands/hexists/ for details.
*
Expand Down Expand Up @@ -1643,6 +1640,81 @@ export class BaseClient {
return this.createWritePromise(createHStrlen(key, field));
}

/**
* Returns a random field name from the hash value stored at `key`.
*
* See https://valkey.io/commands/hrandfield/ for more details.
*
* since Valkey version 6.2.0.
*
* @param key - The key of the hash.
* @returns A random field name from the hash stored at `key`, or `null` when
* the key does not exist.
*
* @example
* ```typescript
* console.log(await client.hrandfield("myHash")); // Output: 'field'
* ```
*/
public async hrandfield(key: string): Promise<string | null> {
return this.createWritePromise(createHRandField(key));
}

/**
* Retrieves up to `count` random field names from the hash value stored at `key`.
*
* See https://valkey.io/commands/hrandfield/ for more details.
*
* since Valkey version 6.2.0.
*
* @param key - The key of the hash.
* @param count - The number of field names to return.
*
* If `count` is positive, returns unique elements. If negative, allows for duplicates.
* @returns An `array` of random field names from the hash stored at `key`,
* or an `empty array` when the key does not exist.
*
* @example
* ```typescript
* console.log(await client.hrandfieldCount("myHash", 2)); // Output: ['field1', 'field2']
* ```
*/
public async hrandfieldCount(
key: string,
count: number,
): Promise<string[]> {
return this.createWritePromise(createHRandField(key, count));
}

/**
* Retrieves up to `count` random field names along with their values from the hash
* value stored at `key`.
*
* See https://valkey.io/commands/hrandfield/ for more details.
*
* since Valkey version 6.2.0.
*
* @param key - The key of the hash.
* @param count - The number of field names to return.
*
* If `count` is positive, returns unique elements. If negative, allows for duplicates.
* @returns A 2D `array` of `[fieldName, value]` `arrays`, where `fieldName` is a random
* field name from the hash and `value` is the associated value of the field name.
* If the hash does not exist or is empty, the response will be an empty `array`.
*
* @example
* ```typescript
* const result = await client.hrandfieldCountWithValues("myHash", 2);
* console.log(result); // Output: [['field1', 'value1'], ['field2', 'value2']]
* ```
*/
public async hrandfieldWithValues(
key: string,
count: number,
): Promise<[string, string][]> {
return this.createWritePromise(createHRandField(key, count, true));
}

/** Inserts all the specified values at the head of the list stored at `key`.
* `elements` are inserted one after the other to the head of the list, from the leftmost element to the rightmost element.
* If `key` does not exist, it is created as empty list before performing the push operations.
Expand Down
12 changes: 12 additions & 0 deletions node/src/Commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3189,6 +3189,18 @@ export function createHStrlen(
return createCommand(RequestType.HStrlen, [key, field]);
}

/** @internal */
export function createHRandField(
key: string,
count?: number,
withValues?: boolean,
): command_request.Command {
const args = [key];
if (count !== undefined) args.push(count.toString());
if (withValues) args.push("WITHVALUES");
return createCommand(RequestType.HRandField, args);
}

/**
* @internal
*/
Expand Down
76 changes: 57 additions & 19 deletions node/src/Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ import {
createHLen,
createHMGet,
createHScan,
createHRandField,
createHSet,
createHSetNX,
createHStrlen,
Expand Down Expand Up @@ -688,25 +689,6 @@ export class BaseTransaction<T extends BaseTransaction<T>> {
return this.addAndReturn(createHGet(key, field));
}

/**
* Iterates incrementally over a hash.
*
* See https://valkey.io/commands/hscan for more details.
*
* @param key - The key of the set.
* @param cursor - The cursor that points to the next iteration of results. A value of `0` indicates the start of the search.
* @param options - The {@link BaseScanOptions}.
*
* Command Response - An `Array` of the `cursor` and the subset of the hash held by `key`.
* The first element is always the `cursor` for the next iteration of results. `0` will be the `cursor`
* returned on the last iteration of the hash. The second element is always an `Array` of the subset of the
* hash held in `key`. The `Array` in the second element is always a flattened series of `String` pairs,
* where the value is at even indices and the score is at odd indices.
*/
public hscan(key: string, cursor: string, options?: BaseScanOptions): T {
return this.addAndReturn(createHScan(key, cursor, options));
}

/** Sets the specified fields to their respective values in the hash stored at `key`.
* See https://valkey.io/commands/hset/ for details.
*
Expand Down Expand Up @@ -854,6 +836,62 @@ export class BaseTransaction<T extends BaseTransaction<T>> {
return this.addAndReturn(createHStrlen(key, field));
}

/**
* Returns a random field name from the hash value stored at `key`.
*
* See https://valkey.io/commands/hrandfield/ for more details.
*
* since Valkey version 6.2.0.
*
* @param key - The key of the hash.
*
* Command Response - A random field name from the hash stored at `key`, or `null` when
* the key does not exist.
*/
public hrandfield(key: string): T {
return this.addAndReturn(createHRandField(key));
}

/**
* Retrieves up to `count` random field names from the hash value stored at `key`.
*
* See https://valkey.io/commands/hrandfield/ for more details.
*
* since Valkey version 6.2.0.
*
* @param key - The key of the hash.
* @param count - The number of field names to return.
*
* If `count` is positive, returns unique elements. If negative, allows for duplicates.
*
* Command Response - An `array` of random field names from the hash stored at `key`,
* or an `empty array` when the key does not exist.
*/
public hrandfieldCount(key: string, count: number): T {
return this.addAndReturn(createHRandField(key, count));
}

/**
* Retrieves up to `count` random field names along with their values from the hash
* value stored at `key`.
*
* See https://valkey.io/commands/hrandfield/ for more details.
*
* since Valkey version 6.2.0.
*
* @param key - The key of the hash.
* @param count - The number of field names to return.
*
* If `count` is positive, returns unique elements. If negative, allows for duplicates.
*
* Command Response - A 2D `array` of `[fieldName, value]` `arrays`, where `fieldName` is a random
* field name from the hash and `value` is the associated value of the field name.
* If the hash does not exist or is empty, the response will be an empty `array`.
*/
public hrandfieldWithValues(key: string, count: number): T {
return this.addAndReturn(createHRandField(key, count, true));
}

/** Inserts all the specified values at the head of the list stored at `key`.
* `elements` are inserted one after the other to the head of the list, from the leftmost element to the rightmost element.
* If `key` does not exist, it is created as empty list before performing the push operations.
Expand Down
57 changes: 57 additions & 0 deletions node/tests/SharedTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1710,6 +1710,63 @@ export function runBaseTests<Context>(config: {
config.timeout,
);

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

const key1 = uuidv4();
const key2 = uuidv4();

// key does not exist
expect(await client.hrandfield(key1)).toBeNull();
expect(await client.hrandfieldCount(key1, 5)).toEqual([]);
expect(await client.hrandfieldWithValues(key1, 5)).toEqual([]);

const data = { "f 1": "v 1", "f 2": "v 2", "f 3": "v 3" };
const fields = Object.keys(data);
const entries = Object.entries(data);
expect(await client.hset(key1, data)).toEqual(3);

expect(fields).toContain(await client.hrandfield(key1));

// With Count - positive count
let result = await client.hrandfieldCount(key1, 5);
expect(result).toEqual(fields);

// With Count - negative count
result = await client.hrandfieldCount(key1, -5);
expect(result.length).toEqual(5);
result.map((r) => expect(fields).toContain(r));

// With values - positive count
let result2 = await client.hrandfieldWithValues(key1, 5);
expect(result2).toEqual(entries);

// With values - negative count
result2 = await client.hrandfieldWithValues(key1, -5);
expect(result2.length).toEqual(5);
result2.map((r) => expect(entries).toContainEqual(r));

// key exists but holds non hash type value
expect(await client.set(key2, "value")).toEqual("OK");
await expect(client.hrandfield(key2)).rejects.toThrow(
RequestError,
);
await expect(client.hrandfieldCount(key2, 42)).rejects.toThrow(
RequestError,
);
await expect(
client.hrandfieldWithValues(key2, 42),
).rejects.toThrow(RequestError);
}, protocol);
},
config.timeout,
);

it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
`lpush, lpop and lrange with existing and non existing key_%p`,
async (protocol) => {
Expand Down
11 changes: 10 additions & 1 deletion node/tests/TestUtilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ export async function transactionTest(
const key1 = "{key}" + uuidv4(); // string
const key2 = "{key}" + uuidv4(); // string
const key3 = "{key}" + uuidv4(); // string
const key4 = "{key}" + uuidv4();
const key4 = "{key}" + uuidv4(); // hash
const key5 = "{key}" + uuidv4();
const key6 = "{key}" + uuidv4();
const key7 = "{key}" + uuidv4();
Expand Down Expand Up @@ -593,6 +593,12 @@ export async function transactionTest(
responseData.push(["hstrlen(key4, field)", value.length]);
baseTransaction.hlen(key4);
responseData.push(["hlen(key4)", 1]);
baseTransaction.hrandfield(key4);
responseData.push(["hrandfield(key4)", field]);
baseTransaction.hrandfieldCount(key4, -2);
responseData.push(["hrandfieldCount(key4, -2)", [field, field]]);
baseTransaction.hrandfieldWithValues(key4, 2);
responseData.push(["hrandfieldWithValues(key4, 2)", [[field, value]]]);
baseTransaction.hsetnx(key4, field, value);
responseData.push(["hsetnx(key4, field, value)", false]);
baseTransaction.hvals(key4);
Expand All @@ -607,6 +613,9 @@ export async function transactionTest(
responseData.push(["hmget(key4, [field])", [null]]);
baseTransaction.hexists(key4, field);
responseData.push(["hexists(key4, field)", false]);
baseTransaction.hrandfield(key4);
responseData.push(["hrandfield(key4)", null]);

baseTransaction.lpush(key5, [
field + "1",
field + "2",
Expand Down

0 comments on commit 93f28fe

Please sign in to comment.