Skip to content

Commit

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

Signed-off-by: Yury-Fridlyand <yury.fridlyand@improving.com>
Co-authored-by: Andrew Carbonetto <andrew.carbonetto@improving.com>
  • Loading branch information
Yury-Fridlyand and acarbonetto authored Aug 1, 2024
1 parent efb6577 commit a0c8f07
Show file tree
Hide file tree
Showing 9 changed files with 803 additions and 11 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 SORT commands ([#2028](https://github.com/valkey-io/valkey-glide/pull/2028))
* Node: Added LASTSAVE command ([#2059](https://github.com/valkey-io/valkey-glide/pull/2059))
* Node: Added LCS command ([#2049](https://github.com/valkey-io/valkey-glide/pull/2049))
* Node: Added MSETNX command ([#2046](https://github.com/valkey-io/valkey-glide/pull/2046))
Expand Down
4 changes: 4 additions & 0 deletions node/npm/glide/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ function initialize() {
RangeByLex,
ReadFrom,
RedisCredentials,
SortClusterOptions,
SortOptions,
SortedSetRange,
StreamTrimOptions,
StreamAddOptions,
Expand Down Expand Up @@ -214,6 +216,8 @@ function initialize() {
RangeByLex,
ReadFrom,
RedisCredentials,
SortClusterOptions,
SortOptions,
SortedSetRange,
StreamTrimOptions,
StreamAddOptions,
Expand Down
124 changes: 124 additions & 0 deletions node/src/Commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2804,6 +2804,130 @@ export function createZIncrBy(
]);
}

/**
* Optional arguments to {@link GlideClient.sort|sort}, {@link GlideClient.sortStore|sortStore} and {@link GlideClient.sortReadOnly|sortReadOnly} commands.
*
* See https://valkey.io/commands/sort/ for more details.
*/
export type SortOptions = SortBaseOptions & {
/**
* A pattern to sort by external keys instead of by the elements stored at the key themselves. The
* pattern should contain an asterisk (*) as a placeholder for the element values, where the value
* from the key replaces the asterisk to create the key name. For example, if `key`
* contains IDs of objects, `byPattern` can be used to sort these IDs based on an
* attribute of the objects, like their weights or timestamps.
*/
byPattern?: string;

/**
* A pattern used to retrieve external keys' values, instead of the elements at `key`.
* The pattern should contain an asterisk (`*`) as a placeholder for the element values, where the
* value from `key` replaces the asterisk to create the `key` name. This
* allows the sorted elements to be transformed based on the related keys values. For example, if
* `key` contains IDs of users, `getPatterns` can be used to retrieve
* specific attributes of these users, such as their names or email addresses. E.g., if
* `getPatterns` is `name_*`, the command will return the values of the keys
* `name_<element>` for each sorted element. Multiple `getPatterns`
* arguments can be provided to retrieve multiple attributes. The special value `#` can
* be used to include the actual element from `key` being sorted. If not provided, only
* the sorted elements themselves are returned.
*/
getPatterns?: string[];
};

type SortBaseOptions = {
/**
* Limiting the range of the query by setting offset and result count. See {@link Limit} class for
* more information.
*/
limit?: Limit;

/** Options for sorting order of elements. */
orderBy?: SortOrder;

/**
* When `true`, sorts elements lexicographically. When `false` (default),
* sorts elements numerically. Use this when the list, set, or sorted set contains string values
* that cannot be converted into double precision floating point numbers.
*/
isAlpha?: boolean;
};

/**
* Optional arguments to {@link GlideClusterClient.sort|sort}, {@link GlideClusterClient.sortStore|sortStore} and {@link GlideClusterClient.sortReadOnly|sortReadOnly} commands.
*
* See https://valkey.io/commands/sort/ for more details.
*/
export type SortClusterOptions = SortBaseOptions;

/**
* The `LIMIT` argument is commonly used to specify a subset of results from the
* matching elements, similar to the `LIMIT` clause in SQL (e.g., `SELECT LIMIT offset, count`).
*/
export type Limit = {
/** The starting position of the range, zero based. */
offset: number;
/** The maximum number of elements to include in the range. A negative count returns all elements from the offset. */
count: number;
};

/** @internal */
export function createSort(
key: string,
options?: SortOptions,
destination?: string,
): command_request.Command {
return createSortImpl(RequestType.Sort, key, options, destination);
}

/** @internal */
export function createSortReadOnly(
key: string,
options?: SortOptions,
): command_request.Command {
return createSortImpl(RequestType.SortReadOnly, key, options);
}

/** @internal */
function createSortImpl(
cmd: RequestType,
key: string,
options?: SortOptions,
destination?: string,
): command_request.Command {
const args: string[] = [key];

if (options) {
if (options.limit) {
args.push(
"LIMIT",
options.limit.offset.toString(),
options.limit.count.toString(),
);
}

if (options.orderBy) {
args.push(options.orderBy);
}

if (options.isAlpha) {
args.push("ALPHA");
}

if (options.byPattern) {
args.push("BY", options.byPattern);
}

if (options.getPatterns) {
options.getPatterns.forEach((p) => args.push("GET", p));
}
}

if (destination) args.push("STORE", destination);

return createCommand(cmd, args);
}

/**
* @internal
*/
Expand Down
99 changes: 99 additions & 0 deletions node/src/GlideClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
BaseClient,
BaseClientConfiguration,
PubSubMsg,
ReadFrom, // eslint-disable-line @typescript-eslint/no-unused-vars
ReturnType,
} from "./BaseClient";
import {
Expand All @@ -15,6 +16,7 @@ import {
FunctionListResponse,
InfoOptions,
LolwutOptions,
SortOptions,
createClientGetName,
createClientId,
createConfigGet,
Expand All @@ -38,6 +40,8 @@ import {
createPublish,
createRandomKey,
createSelect,
createSort,
createSortReadOnly,
createTime,
} from "./Commands";
import { connection_request } from "./ProtobufMessage";
Expand Down Expand Up @@ -612,6 +616,101 @@ export class GlideClient extends BaseClient {
return this.createWritePromise(createPublish(message, channel));
}

/**
* Sorts the elements in the list, set, or sorted set at `key` and returns the result.
*
* The `sort` command can be used to sort elements based on different criteria and
* apply transformations on sorted elements.
*
* To store the result into a new key, see {@link sortStore}.
*
* See https://valkey.io/commands/sort for more details.
*
* @param key - The key of the list, set, or sorted set to be sorted.
* @param options - The {@link SortOptions}.
* @returns An `Array` of sorted elements.
*
* @example
* ```typescript
* await client.hset("user:1", new Map([["name", "Alice"], ["age", "30"]]));
* await client.hset("user:2", new Map([["name", "Bob"], ["age", "25"]]));
* await client.lpush("user_ids", ["2", "1"]);
* const result = await client.sort("user_ids", { byPattern: "user:*->age", getPattern: ["user:*->name"] });
* console.log(result); // Output: [ 'Bob', 'Alice' ] - Returns a list of the names sorted by age
* ```
*/
public async sort(
key: string,
options?: SortOptions,
): Promise<(string | null)[]> {
return this.createWritePromise(createSort(key, options));
}

/**
* Sorts the elements in the list, set, or sorted set at `key` and returns the result.
*
* The `sortReadOnly` command can be used to sort elements based on different criteria and
* apply transformations on sorted elements.
*
* This command is routed depending on the client's {@link ReadFrom} strategy.
*
* since Valkey version 7.0.0.
*
* @param key - The key of the list, set, or sorted set to be sorted.
* @param options - The {@link SortOptions}.
* @returns An `Array` of sorted elements
*
* @example
* ```typescript
* await client.hset("user:1", new Map([["name", "Alice"], ["age", "30"]]));
* await client.hset("user:2", new Map([["name", "Bob"], ["age", "25"]]));
* await client.lpush("user_ids", ["2", "1"]);
* const result = await client.sortReadOnly("user_ids", { byPattern: "user:*->age", getPattern: ["user:*->name"] });
* console.log(result); // Output: [ 'Bob', 'Alice' ] - Returns a list of the names sorted by age
* ```
*/
public async sortReadOnly(
key: string,
options?: SortOptions,
): Promise<(string | null)[]> {
return this.createWritePromise(createSortReadOnly(key, options));
}

/**
* Sorts the elements in the list, set, or sorted set at `key` and stores the result in
* `destination`.
*
* The `sort` command can be used to sort elements based on different criteria and
* apply transformations on sorted elements, and store the result in a new key.
*
* To get the sort result without storing it into a key, see {@link sort} or {@link sortReadOnly}.
*
* See https://valkey.io/commands/sort for more details.
*
* @remarks When in cluster mode, `destination` and `key` must map to the same hash slot.
* @param key - The key of the list, set, or sorted set to be sorted.
* @param destination - The key where the sorted result will be stored.
* @param options - The {@link SortOptions}.
* @returns The number of elements in the sorted key stored at `destination`.
*
* @example
* ```typescript
* await client.hset("user:1", new Map([["name", "Alice"], ["age", "30"]]));
* await client.hset("user:2", new Map([["name", "Bob"], ["age", "25"]]));
* await client.lpush("user_ids", ["2", "1"]);
* const sortedElements = await client.sortStore("user_ids", "sortedList", { byPattern: "user:*->age", getPattern: ["user:*->name"] });
* console.log(sortedElements); // Output: 2 - number of elements sorted and stored
* console.log(await client.lrange("sortedList", 0, -1)); // Output: [ 'Bob', 'Alice' ] - Returns a list of the names sorted by age stored in `sortedList`
* ```
*/
public async sortStore(
key: string,
destination: string,
options?: SortOptions,
): Promise<number> {
return this.createWritePromise(createSort(key, options, destination));
}

/**
* Returns `UNIX TIME` of the last DB save timestamp or startup timestamp if no save
* was made since then.
Expand Down
Loading

0 comments on commit a0c8f07

Please sign in to comment.