From daeef6181b9eb0854da84d059fe34b8f47327304 Mon Sep 17 00:00:00 2001 From: Shoham Elias Date: Tue, 10 Sep 2024 14:36:28 +0000 Subject: [PATCH] add node Signed-off-by: Shoham Elias --- node/src/BaseClient.ts | 110 ++++++++ node/src/Commands.ts | 30 +-- node/src/GlideClient.ts | 107 +------- node/src/GlideClusterClient.ts | 99 +------ node/src/Transaction.ts | 163 ++++-------- node/tests/GlideClient.test.ts | 256 +----------------- node/tests/SharedTests.ts | 323 ++++++++++++++++++++++- python/python/tests/test_async_client.py | 2 +- 8 files changed, 488 insertions(+), 602 deletions(-) diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts index 8b394586a4..82a693ad24 100644 --- a/node/src/BaseClient.ts +++ b/node/src/BaseClient.ts @@ -47,6 +47,7 @@ import { ScoreFilter, SearchOrigin, SetOptions, + SortOptions, StreamAddOptions, StreamClaimOptions, StreamGroupOptions, @@ -167,6 +168,8 @@ import { createSet, createSetBit, createSetRange, + createSort, + createSortReadOnly, createStrlen, createTTL, createTouch, @@ -6978,6 +6981,113 @@ export class BaseClient { return this.createWritePromise(createPubSubNumSub(channels)); } + /** + * 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 {@link https://valkey.io/commands/sort/|valkey.io} for more details. + * @remarks When in cluster mode, both `key` and the patterns specified in {@link SortOptions.byPattern} + * and {@link SortOptions.getPatterns} must map to the same hash slot. The use of {@link SortOptions.byPattern} + * and {@link SortOptions.getPatterns} in cluster mode is supported since Valkey version 8.0. + * @param key - The key of the list, set, or sorted set to be sorted. + * @param options - (Optional) The {@link SortOptions} and {@link DecoderOption}. + * + * @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: GlideString, + options?: SortOptions & DecoderOption, + ): Promise<(GlideString | null)[]> { + return this.createWritePromise(createSort(key, options), { + decoder: options?.decoder, + }); + } + + /** + * 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. + * + * @see {@link https://valkey.io/commands/sort/|valkey.io} for more details. + * @remarks Since Valkey version 7.0.0. + * @remarks When in cluster mode, both `key` and the patterns specified in {@link SortOptions.byPattern} + * and {@link SortOptions.getPatterns} must map to the same hash slot. The use of {@link SortOptions.byPattern} + * and {@link SortOptions.getPatterns} in cluster mode is supported since Valkey version 8.0. + * @param key - The key of the list, set, or sorted set to be sorted. + * @param options - (Optional) The {@link SortOptions} and {@link DecoderOption}. + * @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: GlideString, + options?: SortOptions & DecoderOption, + ): Promise<(GlideString | null)[]> { + return this.createWritePromise(createSortReadOnly(key, options), { + decoder: options?.decoder, + }); + } + + /** + * 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 {@link https://valkey.io/commands/sort|valkey.io} for more details. + * @remarks When in cluster mode, `key`, `destination` and the patterns specified in {@link SortOptions.byPattern} + * and {@link SortOptions.getPatterns} must map to the same hash slot. The use of {@link SortOptions.byPattern} + * and {@link SortOptions.getPatterns} in cluster mode is supported since Valkey version 8.0. + * + * @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 - (Optional) 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: GlideString, + destination: GlideString, + options?: SortOptions, + ): Promise { + return this.createWritePromise(createSort(key, options, destination)); + } + /** * @internal */ diff --git a/node/src/Commands.ts b/node/src/Commands.ts index 2d26178332..60040a3725 100644 --- a/node/src/Commands.ts +++ b/node/src/Commands.ts @@ -3518,20 +3518,30 @@ export function createZIncrBy( } /** - * Optional arguments to {@link GlideClient.sort|sort}, {@link GlideClient.sortStore|sortStore} and {@link GlideClient.sortReadOnly|sortReadOnly} commands. + * Optional arguments to {@link BaseClient.sort|sort}, {@link BaseClient.sortStore|sortStore} and {@link BaseClient.sortReadOnly|sortReadOnly} commands. * * See https://valkey.io/commands/sort/ for more details. + * + * @remarks When in cluster mode, {@link SortOptions.byPattern|byPattern} and {@link SortOptions.getPatterns|getPattern} must map to the same hash + * slot as the key, and this is supported only since Valkey version 8.0. */ -export type SortOptions = SortBaseOptions & { +export type SortOptions = { /** * 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. + * Supported in cluster mode since Valkey version 8.0 and above. */ byPattern?: GlideString; + /** + * Limiting the range of the query by setting offset and result count. See {@link Limit} class for + * more information. + */ + limit?: Limit; + /** * 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 @@ -3544,16 +3554,9 @@ export type SortOptions = SortBaseOptions & { * 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. + * Supported in cluster mode since Valkey version 8.0 and above. */ getPatterns?: GlideString[]; -}; - -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; @@ -3566,13 +3569,6 @@ type SortBaseOptions = { 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`). diff --git a/node/src/GlideClient.ts b/node/src/GlideClient.ts index 3492fd715e..33760eba8f 100644 --- a/node/src/GlideClient.ts +++ b/node/src/GlideClient.ts @@ -9,8 +9,7 @@ import { Decoder, DecoderOption, GlideString, - PubSubMsg, - ReadFrom, // eslint-disable-line @typescript-eslint/no-unused-vars + PubSubMsg, // eslint-disable-line @typescript-eslint/no-unused-vars ReturnType, } from "./BaseClient"; import { @@ -21,7 +20,6 @@ import { FunctionStatsFullResponse, InfoOptions, LolwutOptions, - SortOptions, createClientGetName, createClientId, createConfigGet, @@ -50,8 +48,6 @@ import { createPublish, createRandomKey, createSelect, - createSort, - createSortReadOnly, createTime, createUnWatch, } from "./Commands"; @@ -858,107 +854,6 @@ 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 {@link https://valkey.io/commands/sort/|valkey.io} for more details. - * - * @param key - The key of the list, set, or sorted set to be sorted. - * @param options - (Optional) The {@link SortOptions} and {@link DecoderOption}. - * - * @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: GlideString, - options?: SortOptions & DecoderOption, - ): Promise<(GlideString | null)[]> { - return this.createWritePromise(createSort(key, options), { - decoder: options?.decoder, - }); - } - - /** - * 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. - * - * @see {@link https://valkey.io/commands/sort/|valkey.io} for more details. - * @remarks Since Valkey version 7.0.0. - * - * @param key - The key of the list, set, or sorted set to be sorted. - * @param options - (Optional) The {@link SortOptions} and {@link DecoderOption}. - * @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: GlideString, - options?: SortOptions & DecoderOption, - ): Promise<(GlideString | null)[]> { - return this.createWritePromise(createSortReadOnly(key, options), { - decoder: options?.decoder, - }); - } - - /** - * 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 {@link https://valkey.io/commands/sort|valkey.io} 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 - (Optional) 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: GlideString, - destination: GlideString, - options?: SortOptions, - ): Promise { - 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. diff --git a/node/src/GlideClusterClient.ts b/node/src/GlideClusterClient.ts index 0cb43f00a4..63f9fe63d7 100644 --- a/node/src/GlideClusterClient.ts +++ b/node/src/GlideClusterClient.ts @@ -9,8 +9,7 @@ import { Decoder, DecoderOption, GlideString, - PubSubMsg, - ReadFrom, // eslint-disable-line @typescript-eslint/no-unused-vars + PubSubMsg, // eslint-disable-line @typescript-eslint/no-unused-vars ReturnType, } from "./BaseClient"; import { @@ -21,7 +20,6 @@ import { FunctionStatsSingleResponse, InfoOptions, LolwutOptions, - SortClusterOptions, createClientGetName, createClientId, createConfigGet, @@ -52,8 +50,6 @@ import { createPublish, createPubsubShardChannels, createRandomKey, - createSort, - createSortReadOnly, createTime, createUnWatch, } from "./Commands"; @@ -1290,99 +1286,6 @@ export class GlideClusterClient extends BaseClient { return this.createWritePromise(createPubSubShardNumSub(channels)); } - /** - * 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 {@link https://valkey.io/commands/sort/|valkey.io} for details. - * - * @param key - The key of the list, set, or sorted set to be sorted. - * @param options - (Optional) {@link SortClusterOptions} and {@link DecoderOption}. - * @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: GlideString, - options?: SortClusterOptions & DecoderOption, - ): Promise { - return this.createWritePromise(createSort(key, options), { - decoder: options?.decoder, - }); - } - - /** - * 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. - * - * @remarks Since Valkey version 7.0.0. - * - * @param key - The key of the list, set, or sorted set to be sorted. - * @param options - (Optional) See {@link SortClusterOptions} and {@link DecoderOption}. - * @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: GlideString, - options?: SortClusterOptions & DecoderOption, - ): Promise { - return this.createWritePromise(createSortReadOnly(key, options), { - decoder: options?.decoder, - }); - } - - /** - * 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 {@link https://valkey.io/commands/sort/|valkey.io} for 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 - (Optional) See {@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: GlideString, - destination: GlideString, - options?: SortClusterOptions, - ): Promise { - 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. diff --git a/node/src/Transaction.ts b/node/src/Transaction.ts index 87d86f8db3..52835a0fc8 100644 --- a/node/src/Transaction.ts +++ b/node/src/Transaction.ts @@ -6,8 +6,7 @@ import { BaseClient, // eslint-disable-line @typescript-eslint/no-unused-vars GlideRecord, GlideString, - HashDataType, - ReadFrom, // eslint-disable-line @typescript-eslint/no-unused-vars + HashDataType, // eslint-disable-line @typescript-eslint/no-unused-vars SortedSetDataType, // eslint-disable-line @typescript-eslint/no-unused-vars convertFieldsAndValuesForHset, } from "./BaseClient"; @@ -57,7 +56,6 @@ import { ScoreFilter, SearchOrigin, SetOptions, - SortClusterOptions, SortOptions, StreamAddOptions, StreamClaimOptions, @@ -3949,43 +3947,6 @@ export class BaseTransaction> { public pubsubNumSub(channels?: string[]): T { return this.addAndReturn(createPubSubNumSub(channels)); } -} - -/** - * Extends BaseTransaction class for Redis standalone commands. - * Transactions allow the execution of a group of commands in a single step. - * - * Command Response: - * An array of command responses is returned by the GlideClient.exec command, in the order they were given. - * Each element in the array represents a command given to the transaction. - * The response for each command depends on the executed Redis command. - * Specific response types are documented alongside each method. - * - * @example - * ```typescript - * const transaction = new Transaction() - * .set("key", "value") - * .select(1) /// Standalone command - * .get("key"); - * const result = await GlideClient.exec(transaction); - * console.log(result); // Output: ['OK', 'OK', null] - * ``` - */ -export class Transaction extends BaseTransaction { - /// TODO: add MOVE, SLAVEOF and all SENTINEL commands - - /** - * Change the currently selected database. - * - * @see {@link https://valkey.io/commands/select/|valkey.io} for details. - * - * @param index - The index of the database to select. - * - * Command Response - A simple `"OK"` response. - */ - public select(index: number): Transaction { - return this.addAndReturn(createSelect(index)); - } /** * Sorts the elements in the list, set, or sorted set at `key` and returns the result. @@ -3996,13 +3957,16 @@ export class Transaction extends BaseTransaction { * To store the result into a new key, see {@link sortStore}. * * @see {@link https://valkey.io/commands/sort/|valkey.io} for more details. + * @remarks When in cluster mode, both `key` and the patterns specified in {@link SortOptions.byPattern} + * and {@link SortOptions.getPatterns} must map to the same hash slot. The use of {@link SortOptions.byPattern} + * and {@link SortOptions.getPatterns} in cluster mode is supported since Valkey version 8.0. * * @param key - The key of the list, set, or sorted set to be sorted. * @param options - (Optional) {@link SortOptions}. * * Command Response - An `Array` of sorted elements. */ - public sort(key: GlideString, options?: SortOptions): Transaction { + public sort(key: GlideString, options?: SortOptions): T { return this.addAndReturn(createSort(key, options)); } @@ -4015,13 +3979,16 @@ export class Transaction extends BaseTransaction { * This command is routed depending on the client's {@link ReadFrom} strategy. * * @remarks Since Valkey version 7.0.0. + * @remarks When in cluster mode, both `key` and the patterns specified in {@link SortOptions.byPattern} + * and {@link SortOptions.getPatterns} must map to the same hash slot. The use of {@link SortOptions.byPattern} + * and {@link SortOptions.getPatterns} in cluster mode is supported since Valkey version 8.0. * * @param key - The key of the list, set, or sorted set to be sorted. * @param options - (Optional) {@link SortOptions}. * * Command Response - An `Array` of sorted elements */ - public sortReadOnly(key: GlideString, options?: SortOptions): Transaction { + public sortReadOnly(key: GlideString, options?: SortOptions): T { return this.addAndReturn(createSortReadOnly(key, options)); } @@ -4035,6 +4002,9 @@ export class Transaction extends BaseTransaction { * To get the sort result without storing it into a key, see {@link sort} or {@link sortReadOnly}. * * @see {@link https://valkey.io/commands/sort/|valkey.io} for more details. + * @remarks When in cluster mode, `key`, `destination` and the patterns specified in {@link SortOptions.byPattern} + * and {@link SortOptions.getPatterns} must map to the same hash slot. The use of {@link SortOptions.byPattern} + * and {@link SortOptions.getPatterns} in cluster mode is supported since Valkey version 8.0. * * @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. @@ -4046,9 +4016,46 @@ export class Transaction extends BaseTransaction { key: GlideString, destination: GlideString, options?: SortOptions, - ): Transaction { + ): T { return this.addAndReturn(createSort(key, options, destination)); } +} + +/** + * Extends BaseTransaction class for Redis standalone commands. + * Transactions allow the execution of a group of commands in a single step. + * + * Command Response: + * An array of command responses is returned by the GlideClient.exec command, in the order they were given. + * Each element in the array represents a command given to the transaction. + * The response for each command depends on the executed Redis command. + * Specific response types are documented alongside each method. + * + * @example + * ```typescript + * const transaction = new Transaction() + * .set("key", "value") + * .select(1) /// Standalone command + * .get("key"); + * const result = await GlideClient.exec(transaction); + * console.log(result); // Output: ['OK', 'OK', null] + * ``` + */ +export class Transaction extends BaseTransaction { + /// TODO: add MOVE, SLAVEOF and all SENTINEL commands + + /** + * Change the currently selected database. + * + * @see {@link https://valkey.io/commands/select/|valkey.io} for details. + * + * @param index - The index of the database to select. + * + * Command Response - A simple `"OK"` response. + */ + public select(index: number): Transaction { + return this.addAndReturn(createSelect(index)); + } /** * Copies the value stored at the `source` to the `destination` key. If `destinationDB` is specified, @@ -4120,76 +4127,6 @@ export class Transaction extends BaseTransaction { export class ClusterTransaction extends BaseTransaction { /// TODO: add all CLUSTER commands - /** - * 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 {@link https://valkey.io/commands/sort/|valkey.io} for more details. - * - * @param key - The key of the list, set, or sorted set to be sorted. - * @param options - (Optional) {@link SortClusterOptions}. - * - * Command Response - An `Array` of sorted elements. - */ - public sort( - key: GlideString, - options?: SortClusterOptions, - ): ClusterTransaction { - return this.addAndReturn(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. - * - * @see {@link https://valkey.io/commands/sort/|valkey.io} for more details. - * @remarks Since Valkey version 7.0.0. - * - * @param key - The key of the list, set, or sorted set to be sorted. - * @param options - (Optional) {@link SortClusterOptions}. - * - * Command Response - An `Array` of sorted elements - */ - public sortReadOnly( - key: GlideString, - options?: SortClusterOptions, - ): ClusterTransaction { - return this.addAndReturn(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 {@link https://valkey.io/commands/sort|valkey.io} for more details. - * - * @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 - (Optional) {@link SortClusterOptions}. - * - * Command Response - The number of elements in the sorted key stored at `destination`. - */ - public sortStore( - key: GlideString, - destination: GlideString, - options?: SortClusterOptions, - ): ClusterTransaction { - return this.addAndReturn(createSort(key, options, destination)); - } - /** * Copies the value stored at the `source` to the `destination` key. When `replace` is true, * removes the `destination` key first if it already exists, otherwise performs no action. diff --git a/node/tests/GlideClient.test.ts b/node/tests/GlideClient.test.ts index 8efc797900..fe98e8f23a 100644 --- a/node/tests/GlideClient.test.ts +++ b/node/tests/GlideClient.test.ts @@ -15,16 +15,14 @@ import { v4 as uuidv4 } from "uuid"; import { Decoder, GlideClient, - HashDataType, ProtocolVersion, RequestError, - Transaction, + Transaction } from ".."; import { RedisCluster } from "../../utils/TestUtils.js"; import { FlushMode, - FunctionRestorePolicy, - SortOrder, + FunctionRestorePolicy } from "../build-ts/src/Commands"; import { command_request } from "../src/ProtobufMessage"; import { runBaseTests } from "./SharedTests"; @@ -1152,255 +1150,7 @@ describe("GlideClient", () => { }, ); - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - "sort sortstore sort_store sortro sort_ro sortreadonly test_%p", - async (protocol) => { - const client = await GlideClient.createClient( - getClientConfigurationOption(cluster.getAddresses(), protocol), - ); - - const setPrefix = "setKey" + uuidv4(); - const hashPrefix = "hashKey" + uuidv4(); - const list = uuidv4(); - const store = uuidv4(); - const names = ["Alice", "Bob", "Charlie", "Dave", "Eve"]; - const ages = ["30", "25", "35", "20", "40"]; - - for (let i = 0; i < ages.length; i++) { - const fieldValueList: HashDataType = [ - { field: "name", value: names[i] }, - { field: "age", value: ages[i] }, - ]; - expect( - await client.hset(setPrefix + (i + 1), fieldValueList), - ).toEqual(2); - } - - expect(await client.rpush(list, ["3", "1", "5", "4", "2"])).toEqual( - 5, - ); - - expect( - await client.sort(list, { - limit: { offset: 0, count: 2 }, - getPatterns: [setPrefix + "*->name"], - }), - ).toEqual(["Alice", "Bob"]); - - expect( - await client.sort(Buffer.from(list), { - limit: { offset: 0, count: 2 }, - getPatterns: [setPrefix + "*->name"], - orderBy: SortOrder.DESC, - }), - ).toEqual(["Eve", "Dave"]); - - expect( - await client.sort(list, { - limit: { offset: 0, count: 2 }, - byPattern: setPrefix + "*->age", - getPatterns: [setPrefix + "*->name", setPrefix + "*->age"], - orderBy: SortOrder.DESC, - }), - ).toEqual(["Eve", "40", "Charlie", "35"]); - - // test binary decoder - expect( - await client.sort(list, { - limit: { offset: 0, count: 2 }, - byPattern: setPrefix + "*->age", - getPatterns: [setPrefix + "*->name", setPrefix + "*->age"], - orderBy: SortOrder.DESC, - decoder: Decoder.Bytes, - }), - ).toEqual([ - Buffer.from("Eve"), - Buffer.from("40"), - Buffer.from("Charlie"), - Buffer.from("35"), - ]); - - // Non-existent key in the BY pattern will result in skipping the sorting operation - expect(await client.sort(list, { byPattern: "noSort" })).toEqual([ - "3", - "1", - "5", - "4", - "2", - ]); - - // Non-existent key in the GET pattern results in nulls - expect( - await client.sort(list, { - isAlpha: true, - getPatterns: ["missing"], - }), - ).toEqual([null, null, null, null, null]); - - // Missing key in the set - expect(await client.lpush(list, ["42"])).toEqual(6); - expect( - await client.sort(list, { - byPattern: setPrefix + "*->age", - getPatterns: [setPrefix + "*->name"], - }), - ).toEqual([null, "Dave", "Bob", "Alice", "Charlie", "Eve"]); - expect(await client.lpop(list)).toEqual("42"); - - // sort RO - if (!cluster.checkIfServerVersionLessThan("7.0.0")) { - expect( - await client.sortReadOnly(list, { - limit: { offset: 0, count: 2 }, - getPatterns: [setPrefix + "*->name"], - }), - ).toEqual(["Alice", "Bob"]); - - expect( - await client.sortReadOnly(list, { - limit: { offset: 0, count: 2 }, - getPatterns: [setPrefix + "*->name"], - orderBy: SortOrder.DESC, - decoder: Decoder.Bytes, - }), - ).toEqual([Buffer.from("Eve"), Buffer.from("Dave")]); - - expect( - await client.sortReadOnly(Buffer.from(list), { - limit: { offset: 0, count: 2 }, - byPattern: setPrefix + "*->age", - getPatterns: [ - setPrefix + "*->name", - setPrefix + "*->age", - ], - orderBy: SortOrder.DESC, - }), - ).toEqual(["Eve", "40", "Charlie", "35"]); - - // Non-existent key in the BY pattern will result in skipping the sorting operation - expect( - await client.sortReadOnly(list, { byPattern: "noSort" }), - ).toEqual(["3", "1", "5", "4", "2"]); - - // Non-existent key in the GET pattern results in nulls - expect( - await client.sortReadOnly(list, { - isAlpha: true, - getPatterns: ["missing"], - }), - ).toEqual([null, null, null, null, null]); - - // Missing key in the set - expect(await client.lpush(list, ["42"])).toEqual(6); - expect( - await client.sortReadOnly(list, { - byPattern: setPrefix + "*->age", - getPatterns: [setPrefix + "*->name"], - }), - ).toEqual([null, "Dave", "Bob", "Alice", "Charlie", "Eve"]); - expect(await client.lpop(list)).toEqual("42"); - } - - // SORT with STORE - expect( - await client.sortStore(list, store, { - limit: { offset: 0, count: -1 }, - byPattern: setPrefix + "*->age", - getPatterns: [setPrefix + "*->name"], - orderBy: SortOrder.ASC, - }), - ).toEqual(5); - expect(await client.lrange(store, 0, -1)).toEqual([ - "Dave", - "Bob", - "Alice", - "Charlie", - "Eve", - ]); - expect( - await client.sortStore(Buffer.from(list), store, { - byPattern: setPrefix + "*->age", - getPatterns: [setPrefix + "*->name"], - }), - ).toEqual(5); - expect(await client.lrange(store, 0, -1)).toEqual([ - "Dave", - "Bob", - "Alice", - "Charlie", - "Eve", - ]); - - // transaction test - const transaction = new Transaction() - .hset(hashPrefix + 1, [ - { field: "name", value: "Alice" }, - { field: "age", value: "30" }, - ]) - .hset(hashPrefix + 2, { - name: "Bob", - age: "25", - }) - .del([list]) - .lpush(list, ["2", "1"]) - .sort(list, { - byPattern: hashPrefix + "*->age", - getPatterns: [hashPrefix + "*->name"], - }) - .sort(list, { - byPattern: hashPrefix + "*->age", - getPatterns: [hashPrefix + "*->name"], - orderBy: SortOrder.DESC, - }) - .sortStore(list, store, { - byPattern: hashPrefix + "*->age", - getPatterns: [hashPrefix + "*->name"], - }) - .lrange(store, 0, -1) - .sortStore(list, store, { - byPattern: hashPrefix + "*->age", - getPatterns: [hashPrefix + "*->name"], - orderBy: SortOrder.DESC, - }) - .lrange(store, 0, -1); - - if (!cluster.checkIfServerVersionLessThan("7.0.0")) { - transaction - .sortReadOnly(list, { - byPattern: hashPrefix + "*->age", - getPatterns: [hashPrefix + "*->name"], - }) - .sortReadOnly(list, { - byPattern: hashPrefix + "*->age", - getPatterns: [hashPrefix + "*->name"], - orderBy: SortOrder.DESC, - }); - } - - const expectedResult = [ - 2, - 2, - 1, - 2, - ["Bob", "Alice"], - ["Alice", "Bob"], - 2, - ["Bob", "Alice"], - 2, - ["Alice", "Bob"], - ]; - - if (!cluster.checkIfServerVersionLessThan("7.0.0")) { - expectedResult.push(["Bob", "Alice"], ["Alice", "Bob"]); - } - - const result = await client.exec(transaction); - expect(result).toEqual(expectedResult); - - client.close(); - }, - TIMEOUT, - ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( "randomKey test_%p", diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts index 28cd31d95b..11ed07632c 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -2963,7 +2963,9 @@ export function runBaseTests(config: { expect( await client.sinter( [Buffer.from(key1), Buffer.from(key2)], - { decoder: Decoder.Bytes }, + { + decoder: Decoder.Bytes, + }, ), ).toEqual(new Set([Buffer.from("c"), Buffer.from("d")])); @@ -7518,7 +7520,9 @@ export function runBaseTests(config: { ) : await client.exec( new ClusterTransaction().dump(key1), - { decoder: Decoder.Bytes }, + { + decoder: Decoder.Bytes, + }, ); expect(response?.[0]).not.toBeNull(); data = response?.[0] as Buffer; @@ -9169,7 +9173,9 @@ export function runBaseTests(config: { await client.zmpop( [Buffer.from(key2), key1], ScoreFilter.MAX, - { count: 10 }, + { + count: 10, + }, ), ).toEqual([key2, { a2: 0.1, b2: 0.2 }]); @@ -9469,7 +9475,9 @@ export function runBaseTests(config: { [nonExistingKey], ScoreFilter.MAX, 0.55, - { count: 1 }, + { + count: 1, + }, ), ).toBeNull(); @@ -9512,7 +9520,9 @@ export function runBaseTests(config: { [key2], ScoreFilter.MIN, 0.1, - { count: 10 }, + { + count: 10, + }, ); if (result) { @@ -10130,7 +10140,9 @@ export function runBaseTests(config: { await client.xadd( key, [["entry3_field1", "entry3_value1"]], - { id: streamId3 }, + { + id: streamId3, + }, ), ).toEqual(streamId3); @@ -10291,7 +10303,9 @@ export function runBaseTests(config: { await client.xadd( key, [["entry3_field1", "entry3_value1"]], - { id: streamId3 }, + { + id: streamId3, + }, ), ).toEqual(streamId3); @@ -10484,7 +10498,9 @@ export function runBaseTests(config: { const newResult = await client.xreadgroup( groupName, consumerName, - { [key]: ">" }, + { + [key]: ">", + }, ); expect(newResult).toEqual({ [key]: { @@ -10562,7 +10578,9 @@ export function runBaseTests(config: { await client.xadd( key, [["entry2_field1", "entry2_value1"]], - { id: "0-2" }, + { + id: "0-2", + }, ), ).toEqual("0-2"); @@ -10658,7 +10676,9 @@ export function runBaseTests(config: { await client.xadd( key, [["entry2_field1", "entry2_value1"]], - { id: "0-2" }, + { + id: "0-2", + }, ), ).toEqual("0-2"); @@ -10693,7 +10713,9 @@ export function runBaseTests(config: { await client.xadd( key, [["entry3_field1", "entry3_value1"]], - { id: "0-3" }, + { + id: "0-3", + }, ), ).toEqual("0-3"); // using force, we can xclaim the message without reading it @@ -10704,7 +10726,10 @@ export function runBaseTests(config: { "consumer", 0, ["0-3"], - { isForce: true, retryCount: 99 }, + { + isForce: true, + retryCount: 99, + }, ), ).toEqual(["0-3"]); @@ -10770,7 +10795,9 @@ export function runBaseTests(config: { await client.xadd( key, [["entry2_field1", "entry2_value1"]], - { id: "0-2" }, + { + id: "0-2", + }, ), ).toEqual("0-2"); @@ -10825,7 +10852,9 @@ export function runBaseTests(config: { await client.xadd( key, [["entry3_field1", "entry3_value1"]], - { id: "0-3" }, + { + id: "0-3", + }, ), ).toEqual("0-3"); @@ -11511,6 +11540,272 @@ export function runBaseTests(config: { }, config.timeout, ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "sort sortstore sort_store sortro sort_ro sortreadonly test_%p", + async (protocol) => { + await runTest(async (client: BaseClient, cluster: RedisCluster) => { + if ( + cluster.checkIfServerVersionLessThan("7.9.0") && + client instanceof GlideClusterClient + ) { + return; + } + + const setPrefix = "{slot}setKey" + uuidv4(); + const hashPrefix = "{slot}hashKey" + uuidv4(); + const list = "{slot}" + uuidv4(); + const store = "{slot}" + uuidv4(); + const names = ["Alice", "Bob", "Charlie", "Dave", "Eve"]; + const ages = ["30", "25", "35", "20", "40"]; + + for (let i = 0; i < ages.length; i++) { + const fieldValueList: HashDataType = [ + { field: "name", value: names[i] }, + { field: "age", value: ages[i] }, + ]; + expect( + await client.hset(setPrefix + (i + 1), fieldValueList), + ).toEqual(2); + } + + expect( + await client.rpush(list, ["3", "1", "5", "4", "2"]), + ).toEqual(5); + + expect( + await client.sort(list, { + limit: { offset: 0, count: 2 }, + getPatterns: [setPrefix + "*->name"], + }), + ).toEqual(["Alice", "Bob"]); + + expect( + await client.sort(Buffer.from(list), { + limit: { offset: 0, count: 2 }, + getPatterns: [setPrefix + "*->name"], + orderBy: SortOrder.DESC, + }), + ).toEqual(["Eve", "Dave"]); + + expect( + await client.sort(list, { + limit: { offset: 0, count: 2 }, + byPattern: setPrefix + "*->age", + getPatterns: [ + setPrefix + "*->name", + setPrefix + "*->age", + ], + orderBy: SortOrder.DESC, + }), + ).toEqual(["Eve", "40", "Charlie", "35"]); + + // test binary decoder + expect( + await client.sort(list, { + limit: { offset: 0, count: 2 }, + byPattern: setPrefix + "*->age", + getPatterns: [ + setPrefix + "*->name", + setPrefix + "*->age", + ], + orderBy: SortOrder.DESC, + decoder: Decoder.Bytes, + }), + ).toEqual([ + Buffer.from("Eve"), + Buffer.from("40"), + Buffer.from("Charlie"), + Buffer.from("35"), + ]); + + // Non-existent key in the BY pattern will result in skipping the sorting operation + expect( + await client.sort(list, { byPattern: "noSort" }), + ).toEqual(["3", "1", "5", "4", "2"]); + + // Non-existent key in the GET pattern results in nulls + expect( + await client.sort(list, { + isAlpha: true, + getPatterns: ["{slot}missing"], + }), + ).toEqual([null, null, null, null, null]); + + // Missing key in the set + expect(await client.lpush(list, ["42"])).toEqual(6); + expect( + await client.sort(list, { + byPattern: setPrefix + "*->age", + getPatterns: [setPrefix + "*->name"], + }), + ).toEqual([null, "Dave", "Bob", "Alice", "Charlie", "Eve"]); + expect(await client.lpop(list)).toEqual("42"); + + // sort RO + if (!cluster.checkIfServerVersionLessThan("7.0.0")) { + expect( + await client.sortReadOnly(list, { + limit: { offset: 0, count: 2 }, + getPatterns: [setPrefix + "*->name"], + }), + ).toEqual(["Alice", "Bob"]); + + expect( + await client.sortReadOnly(list, { + limit: { offset: 0, count: 2 }, + getPatterns: [setPrefix + "*->name"], + orderBy: SortOrder.DESC, + decoder: Decoder.Bytes, + }), + ).toEqual([Buffer.from("Eve"), Buffer.from("Dave")]); + + expect( + await client.sortReadOnly(Buffer.from(list), { + limit: { offset: 0, count: 2 }, + byPattern: setPrefix + "*->age", + getPatterns: [ + setPrefix + "*->name", + setPrefix + "*->age", + ], + orderBy: SortOrder.DESC, + }), + ).toEqual(["Eve", "40", "Charlie", "35"]); + + // Non-existent key in the BY pattern will result in skipping the sorting operation + expect( + await client.sortReadOnly(list, { + byPattern: "noSort", + }), + ).toEqual(["3", "1", "5", "4", "2"]); + + // Non-existent key in the GET pattern results in nulls + expect( + await client.sortReadOnly(list, { + isAlpha: true, + getPatterns: ["{slot}missing"], + }), + ).toEqual([null, null, null, null, null]); + + // Missing key in the set + expect(await client.lpush(list, ["42"])).toEqual(6); + expect( + await client.sortReadOnly(list, { + byPattern: setPrefix + "*->age", + getPatterns: [setPrefix + "*->name"], + }), + ).toEqual([null, "Dave", "Bob", "Alice", "Charlie", "Eve"]); + expect(await client.lpop(list)).toEqual("42"); + } + + // SORT with STORE + expect( + await client.sortStore(list, store, { + limit: { offset: 0, count: -1 }, + byPattern: setPrefix + "*->age", + getPatterns: [setPrefix + "*->name"], + orderBy: SortOrder.ASC, + }), + ).toEqual(5); + expect(await client.lrange(store, 0, -1)).toEqual([ + "Dave", + "Bob", + "Alice", + "Charlie", + "Eve", + ]); + expect( + await client.sortStore(Buffer.from(list), store, { + byPattern: setPrefix + "*->age", + getPatterns: [setPrefix + "*->name"], + }), + ).toEqual(5); + expect(await client.lrange(store, 0, -1)).toEqual([ + "Dave", + "Bob", + "Alice", + "Charlie", + "Eve", + ]); + + // transaction test + const transaction = + client instanceof GlideClient + ? new Transaction() + : new ClusterTransaction(); + transaction + .hset(hashPrefix + 1, [ + { field: "name", value: "Alice" }, + { field: "age", value: "30" }, + ]) + .hset(hashPrefix + 2, { + name: "Bob", + age: "25", + }) + .del([list]) + .lpush(list, ["2", "1"]) + .sort(list, { + byPattern: hashPrefix + "*->age", + getPatterns: [hashPrefix + "*->name"], + }) + .sort(list, { + byPattern: hashPrefix + "*->age", + getPatterns: [hashPrefix + "*->name"], + orderBy: SortOrder.DESC, + }) + .sortStore(list, store, { + byPattern: hashPrefix + "*->age", + getPatterns: [hashPrefix + "*->name"], + }) + .lrange(store, 0, -1) + .sortStore(list, store, { + byPattern: hashPrefix + "*->age", + getPatterns: [hashPrefix + "*->name"], + orderBy: SortOrder.DESC, + }) + .lrange(store, 0, -1); + + if (!cluster.checkIfServerVersionLessThan("7.0.0")) { + transaction + .sortReadOnly(list, { + byPattern: hashPrefix + "*->age", + getPatterns: [hashPrefix + "*->name"], + }) + .sortReadOnly(list, { + byPattern: hashPrefix + "*->age", + getPatterns: [hashPrefix + "*->name"], + orderBy: SortOrder.DESC, + }); + } + + const expectedResult = [ + 2, + 2, + 1, + 2, + ["Bob", "Alice"], + ["Alice", "Bob"], + 2, + ["Bob", "Alice"], + 2, + ["Alice", "Bob"], + ]; + + if (!cluster.checkIfServerVersionLessThan("7.0.0")) { + expectedResult.push(["Bob", "Alice"], ["Alice", "Bob"]); + } + + const result = + client instanceof GlideClient + ? await client.exec(transaction as Transaction) + : await client.exec(transaction as ClusterTransaction); + expect(result).toEqual(expectedResult); + + client.close(); + }, protocol); + }, + config.timeout, + ); } export function runCommonTests(config: { diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py index d398f777b9..d797abdf52 100644 --- a/python/python/tests/test_async_client.py +++ b/python/python/tests/test_async_client.py @@ -4739,7 +4739,7 @@ async def test_type(self, glide_client: TGlideClient): async def test_sort_and_sort_store_with_get_or_by_args( self, glide_client: TGlideClient ): - if isinstace( + if isinstance( glide_client, GlideClusterClient ) and await check_if_server_version_lt(glide_client, "7.9.0"): return pytest.mark.skip(