Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Node: Add SORT command. #2028

Merged
merged 7 commits into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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:/valkey-io/valkey-glide/pull/2028))
* Node: Added LMOVE command ([#2002](https:/valkey-io/valkey-glide/pull/2002))
* Node: Added GEOPOS command ([#1991](https:/valkey-io/valkey-glide/pull/1991))
* Node: Added BITCOUNT command ([#1982](https:/valkey-io/valkey-glide/pull/1982))
Expand Down
2 changes: 1 addition & 1 deletion node/src/BaseClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ type RedisCredentials = {
password: string;
};

type ReadFrom =
export type ReadFrom =
/** Always get from primary, in order to get the freshest data.*/
| "primary"
/** Spread the requests between all replicas in a round robin manner.
Expand Down
138 changes: 138 additions & 0 deletions node/src/Commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2250,6 +2250,144 @@ 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/ and https://valkey.io/commands/sort_ro/ for more details.
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
*/
export type SortOptions = SortClusterOptions & {
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
/**
* 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.<br>
*
* See https://valkey.io/commands/sort/ for more information.
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
*/
getPatterns?: string[];
};

/**
* Optional arguments to {@link GlideClusterClient.sort|sort}, {@link GlideClusterClient.sortStore|sortStore} and {@link GlideClusterClient.sortReadOnly|sortReadOnly} commands.
*
* See https://valkey.io/commands/sort/ and https://valkey.io/commands/sort_ro/ for more details.
*/
export type SortClusterOptions = {
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
/**
* 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;
};

/**
* 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;
};

/** Defines the sort order for nested results. */
export enum SortOrder {
/** Sort by ascending order. */
ASC = "ASC",
/** Sort by descending order. */
DESC = "DESC",
}

/**
* @internal
*/
export function createSort(
key: string,
options?: SortOptions,
GumpacG marked this conversation as resolved.
Show resolved Hide resolved
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
95 changes: 95 additions & 0 deletions node/src/GlideClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import {
BaseClient,
BaseClientConfiguration,
PubSubMsg,
ReadFrom, // eslint-disable-line @typescript-eslint/no-unused-vars
ReturnType,
} from "./BaseClient";
import {
FlushMode,
InfoOptions,
LolwutOptions,
SortOptions,
createClientGetName,
createClientId,
createConfigGet,
Expand All @@ -32,6 +34,8 @@ import {
createPing,
createPublish,
createSelect,
createSort,
createSortReadOnly,
createTime,
} from "./Commands";
import { connection_request } from "./ProtobufMessage";
Expand Down Expand Up @@ -528,4 +532,95 @@ export class GlideClient extends BaseClient {
public publish(message: string, channel: string): Promise<number> {
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}.
*
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
* @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}.
*
* @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));
}
}
89 changes: 89 additions & 0 deletions node/src/GlideClusterClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import {
BaseClient,
BaseClientConfiguration,
PubSubMsg,
ReadFrom, // eslint-disable-line @typescript-eslint/no-unused-vars
ReturnType,
} from "./BaseClient";
import {
FlushMode,
InfoOptions,
LolwutOptions,
SortClusterOptions,
createClientGetName,
createClientId,
createConfigGet,
Expand All @@ -33,6 +35,8 @@ import {
createLolwut,
createPing,
createPublish,
createSort,
createSortReadOnly,
createTime,
} from "./Commands";
import { RequestError } from "./Errors";
Expand Down Expand Up @@ -907,4 +911,89 @@ export class GlideClusterClient extends BaseClient {
createPublish(message, channel, sharded),
);
}

/**
* 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}.
*
* @param key - The key of the list, set, or sorted set to be sorted.
* @param options - The {@link SortClusterOptions}.
* @returns An `Array` of sorted elements.
*
* @example
* ```typescript
* await client.lpush("mylist", ["3", "1", "2", "a"]);
* const result = await client.sort("mylist", { alpha: true, orderBy: SortOrder.DESC, limit: { offset: 0, count: 3 } });
* console.log(result); // Output: [ 'a', '3', '2' ] - List is sorted in descending order lexicographically
* ```
*/
public async sort(
key: string,
options?: SortClusterOptions,
): Promise<string[]> {
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 SortClusterOptions}.
* @returns An `Array` of sorted elements
*
* @example
* ```typescript
* await client.lpush("mylist", ["3", "1", "2", "a"]);
* const result = await client.sortReadOnly("mylist", { alpha: true, orderBy: SortOrder.DESC, limit: { offset: 0, count: 3 } });
* console.log(result); // Output: [ 'a', '3', '2' ] - List is sorted in descending order lexicographically
* ```
*/
public async sortReadOnly(
key: string,
options?: SortClusterOptions,
): Promise<string[]> {
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}.
*
* @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 SortClusterOptions}.
* @returns The number of elements in the sorted key stored at `destination`.
*
* @example
* ```typescript
* await client.lpush("mylist", ["3", "1", "2", "a"]);
* const sortedElements = await client.sortReadOnly("mylist", "sortedList", { alpha: true, orderBy: SortOrder.DESC, limit: { offset: 0, count: 3 } });
* console.log(sortedElements); // Output: 3 - number of elements sorted and stored
* console.log(await client.lrange("sortedList", 0, -1)); // Output: [ 'a', '3', '2' ] - List is sorted in descending order lexicographically and stored in `sortedList`
* ```
*/
public async sortStore(
key: string,
destination: string,
options?: SortClusterOptions,
): Promise<number> {
return this.createWritePromise(createSort(key, options, destination));
}
}
Loading
Loading