From edcb15a4e42065c82fabd3f9f2fe81fc1404239c Mon Sep 17 00:00:00 2001 From: jonathanl-bq <72158117+jonathanl-bq@users.noreply.github.com> Date: Wed, 14 Aug 2024 14:51:37 -0700 Subject: [PATCH] Node: Add XRANGE command (#2069) * Update changelog Signed-off-by: Jonathan Louie * Implement XRANGE command Signed-off-by: Jonathan Louie * Update CHANGELOG.md Signed-off-by: Jonathan Louie * Apply prettier Signed-off-by: Jonathan Louie * Fix style issue Signed-off-by: Jonathan Louie * Fix failing test Signed-off-by: Jonathan Louie * Fix another failing test Signed-off-by: Jonathan Louie * Address PR comments Signed-off-by: Jonathan Louie * Change return type for xrange Signed-off-by: Jonathan Louie * Try to address remaining PR comments Signed-off-by: Jonathan Louie * Switch to ScoreBoundary Signed-off-by: Jonathan Louie * Remove merge conflict marker Signed-off-by: Jonathan Louie * Apply prettier for SharedTests Signed-off-by: Jonathan Louie * Replace remaining instances of StreamRangeBound with ScoreBoundary Signed-off-by: Jonathan Louie * Update test to use string instead of number Signed-off-by: Jonathan Louie * Change ScoreBoundary to generic Boundary and add check for Redis version to xrange tests Signed-off-by: Jonathan Louie * Adjust documentation and fix linting and tests Signed-off-by: Jonathan Louie * Adjust documentation Signed-off-by: Jonathan Louie --------- Signed-off-by: Jonathan Louie --- CHANGELOG.md | 1 + node/npm/glide/index.ts | 8 +- node/src/BaseClient.ts | 74 +++++++--- node/src/Commands.ts | 87 +++++++----- node/src/Transaction.ts | 47 ++++-- node/tests/SharedTests.ts | 275 +++++++++++++++++++++++++----------- node/tests/TestUtilities.ts | 28 ++-- 7 files changed, 357 insertions(+), 163 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f54f490f7b..b14d1e50c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,7 @@ * Node: Added ZMPOP command ([#1994](https://github.com/valkey-io/valkey-glide/pull/1994)) * Node: Added ZINCRBY command ([#2009](https://github.com/valkey-io/valkey-glide/pull/2009)) * Node: Added BZMPOP command ([#2018](https://github.com/valkey-io/valkey-glide/pull/2018)) +* Node: Added XRANGE command ([#2069](https://github.com/valkey-io/valkey-glide/pull/2069)) * Node: Added PFMERGE command ([#2053](https://github.com/valkey-io/valkey-glide/pull/2053)) * Node: Added WATCH and UNWATCH commands ([#2076](https://github.com/valkey-io/valkey-glide/pull/2076)) * Node: Added WAIT command ([#2113](https://github.com/valkey-io/valkey-glide/pull/2113)) diff --git a/node/npm/glide/index.ts b/node/npm/glide/index.ts index d7070015ff..827d93ee45 100644 --- a/node/npm/glide/index.ts +++ b/node/npm/glide/index.ts @@ -124,8 +124,8 @@ function initialize() { InsertPosition, SetOptions, ZaddOptions, - InfScoreBoundary, - ScoreBoundary, + InfBoundary, + Boundary, UpdateOptions, ProtocolVersion, RangeByIndex, @@ -220,8 +220,8 @@ function initialize() { InsertPosition, SetOptions, ZaddOptions, - InfScoreBoundary, - ScoreBoundary, + InfBoundary, + Boundary, UpdateOptions, ProtocolVersion, RangeByIndex, diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts index 4d46a08c75..36bc60f95e 100644 --- a/node/src/BaseClient.ts +++ b/node/src/BaseClient.ts @@ -22,6 +22,7 @@ import { BitOffsetOptions, BitmapIndexType, BitwiseOperation, + Boundary, CoordOrigin, // eslint-disable-line @typescript-eslint/no-unused-vars ExpireOptions, GeoAddOptions, @@ -41,7 +42,6 @@ import { RangeByLex, RangeByScore, ReturnTypeXinfoStream, - ScoreBoundary, ScoreFilter, SearchOrigin, SetOptions, @@ -176,6 +176,7 @@ import { createXInfoStream, createXLen, createXPending, + createXRange, createXRead, createXTrim, createZAdd, @@ -2980,6 +2981,45 @@ export class BaseClient { return this.createWritePromise(scriptInvocation); } + /** + * Returns stream entries matching a given range of entry IDs. + * + * See https://valkey.io/commands/xrange for more details. + * + * @param key - The key of the stream. + * @param start - The starting stream entry ID bound for the range. + * - Use `value` to specify a stream entry ID. + * - Use `isInclusive: false` to specify an exclusive bounded stream entry ID. This is only available starting with Valkey version 6.2.0. + * - Use `InfBoundary.NegativeInfinity` to start with the minimum available ID. + * @param end - The ending stream entry ID bound for the range. + * - Use `value` to specify a stream entry ID. + * - Use `isInclusive: false` to specify an exclusive bounded stream entry ID. This is only available starting with Valkey version 6.2.0. + * - Use `InfBoundary.PositiveInfinity` to end with the maximum available ID. + * @param count - An optional argument specifying the maximum count of stream entries to return. + * If `count` is not provided, all stream entries in the range will be returned. + * @returns A map of stream entry ids, to an array of entries, or `null` if `count` is negative. + * + * @example + * ```typescript + * await client.xadd("mystream", [["field1", "value1"]], {id: "0-1"}); + * await client.xadd("mystream", [["field2", "value2"], ["field2", "value3"]], {id: "0-2"}); + * console.log(await client.xrange("mystream", InfBoundary.NegativeInfinity, InfBoundary.PositiveInfinity)); + * // Output: + * // { + * // "0-1": [["field1", "value1"]], + * // "0-2": [["field2", "value2"], ["field2", "value3"]], + * // } // Indicates the stream entry IDs and their associated field-value pairs for all stream entries in "mystream". + * ``` + */ + public async xrange( + key: string, + start: Boundary, + end: Boundary, + count?: number, + ): Promise | null> { + return this.createWritePromise(createXRange(key, start, end, count)); + } + /** Adds members with their scores to the sorted set stored at `key`. * If a member is already a part of the sorted set, its score is updated. * See https://valkey.io/commands/zadd/ for more details. @@ -3277,7 +3317,7 @@ export class BaseClient { * @example * ```typescript * // Example usage of the zcount method to count members in a sorted set within a score range - * const result = await client.zcount("my_sorted_set", { value: 5.0, isInclusive: true }, InfScoreBoundary.PositiveInfinity); + * const result = await client.zcount("my_sorted_set", { value: 5.0, isInclusive: true }, InfBoundary.PositiveInfinity); * console.log(result); // Output: 2 - Indicates that there are 2 members with scores between 5.0 (inclusive) and +inf in the sorted set "my_sorted_set". * ``` * @@ -3290,8 +3330,8 @@ export class BaseClient { */ public zcount( key: string, - minScore: ScoreBoundary, - maxScore: ScoreBoundary, + minScore: Boundary, + maxScore: Boundary, ): Promise { return this.createWritePromise(createZCount(key, minScore, maxScore)); } @@ -3321,7 +3361,7 @@ export class BaseClient { * ```typescript * // Example usage of zrange method to retrieve members within a score range in ascending order * const result = await client.zrange("my_sorted_set", { - * start: InfScoreBoundary.NegativeInfinity, + * start: InfBoundary.NegativeInfinity, * stop: { value: 3, isInclusive: false }, * type: "byScore", * }); @@ -3363,7 +3403,7 @@ export class BaseClient { * ```typescript * // Example usage of zrangeWithScores method to retrieve members within a score range with their scores * const result = await client.zrangeWithScores("my_sorted_set", { - * start: InfScoreBoundary.NegativeInfinity, + * start: InfBoundary.NegativeInfinity, * stop: { value: 3, isInclusive: false }, * type: "byScore", * }); @@ -3409,7 +3449,7 @@ export class BaseClient { * ```typescript * // Example usage of zrangeStore method to retrieve members within a score range in ascending order and store in "destination_key" * const result = await client.zrangeStore("destination_key", "my_sorted_set", { - * start: InfScoreBoundary.NegativeInfinity, + * start: InfBoundary.NegativeInfinity, * stop: { value: 3, isInclusive: false }, * type: "byScore", * }); @@ -3805,14 +3845,14 @@ export class BaseClient { * @example * ```typescript * // Example usage of zremRangeByLex method when the sorted set does not exist - * const result = await client.zremRangeByLex("non_existing_sorted_set", InfScoreBoundary.NegativeInfinity, { value: "e" }); + * const result = await client.zremRangeByLex("non_existing_sorted_set", InfBoundary.NegativeInfinity, { value: "e" }); * console.log(result); // Output: 0 - Indicates that no elements were removed. * ``` */ public zremRangeByLex( key: string, - minLex: ScoreBoundary, - maxLex: ScoreBoundary, + minLex: Boundary, + maxLex: Boundary, ): Promise { return this.createWritePromise( createZRemRangeByLex(key, minLex, maxLex), @@ -3832,7 +3872,7 @@ export class BaseClient { * @example * ```typescript * // Example usage of zremRangeByScore method to remove members from a sorted set based on score range - * const result = await client.zremRangeByScore("my_sorted_set", { value: 5.0, isInclusive: true }, InfScoreBoundary.PositiveInfinity); + * const result = await client.zremRangeByScore("my_sorted_set", { value: 5.0, isInclusive: true }, InfBoundary.PositiveInfinity); * console.log(result); // Output: 2 - Indicates that 2 members with scores between 5.0 (inclusive) and +inf have been removed from the sorted set "my_sorted_set". * ``` * @@ -3845,8 +3885,8 @@ export class BaseClient { */ public zremRangeByScore( key: string, - minScore: ScoreBoundary, - maxScore: ScoreBoundary, + minScore: Boundary, + maxScore: Boundary, ): Promise { return this.createWritePromise( createZRemRangeByScore(key, minScore, maxScore), @@ -3867,7 +3907,7 @@ export class BaseClient { * * @example * ```typescript - * const result = await client.zlexcount("my_sorted_set", {value: "c"}, InfScoreBoundary.PositiveInfinity); + * const result = await client.zlexcount("my_sorted_set", {value: "c"}, InfBoundary.PositiveInfinity); * console.log(result); // Output: 2 - Indicates that there are 2 members with lex scores between "c" (inclusive) and positive infinity in the sorted set "my_sorted_set". * ``` * @@ -3879,8 +3919,8 @@ export class BaseClient { */ public async zlexcount( key: string, - minLex: ScoreBoundary, - maxLex: ScoreBoundary, + minLex: Boundary, + maxLex: Boundary, ): Promise { return this.createWritePromise(createZLexCount(key, minLex, maxLex)); } @@ -4132,7 +4172,7 @@ export class BaseClient { * ```typescript * console.log(await client.xpending("my_stream", "my_group"), { * start: { value: "0-1", isInclusive: true }, - * end: InfScoreBoundary.PositiveInfinity, + * end: InfBoundary.PositiveInfinity, * count: 2, * consumer: "consumer1" * }); // Output: diff --git a/node/src/Commands.ts b/node/src/Commands.ts index d98d683748..dee2022c9d 100644 --- a/node/src/Commands.ts +++ b/node/src/Commands.ts @@ -1518,35 +1518,35 @@ export function createZMScore( return createCommand(RequestType.ZMScore, [key, ...members]); } -export enum InfScoreBoundary { +export enum InfBoundary { /** - * Positive infinity bound for sorted set. + * Positive infinity bound. */ PositiveInfinity = "+", /** - * Negative infinity bound for sorted set. + * Negative infinity bound. */ NegativeInfinity = "-", } /** - * Defines where to insert new elements into a list. + * Defines the boundaries of a range. */ -export type ScoreBoundary = +export type Boundary = /** - * Represents an lower/upper boundary in a sorted set. + * Represents an lower/upper boundary. */ - | InfScoreBoundary + | InfBoundary /** - * Represents a specific numeric score boundary in a sorted set. + * Represents a specific boundary. */ | { /** - * The score value. + * The comparison value. */ value: T; /** - * Whether the score value is inclusive. Defaults to True. + * Whether the value is inclusive. Defaults to `true`. */ isInclusive?: boolean; }; @@ -1574,11 +1574,11 @@ type SortedSetRange = { /** * The start boundary. */ - start: ScoreBoundary; + start: Boundary; /** * The stop boundary. */ - stop: ScoreBoundary; + stop: Boundary; /** * The limit argument for a range query. * Represents a limit argument for a range query in a sorted set to @@ -1605,10 +1605,10 @@ export type RangeByLex = SortedSetRange & { type: "byLex" }; /** Returns a string representation of a score boundary as a command argument. */ function getScoreBoundaryArg( - score: ScoreBoundary | ScoreBoundary, + score: Boundary | Boundary, ): string { if (typeof score === "string") { - // InfScoreBoundary + // InfBoundary return score + "inf"; } @@ -1620,11 +1620,9 @@ function getScoreBoundaryArg( } /** Returns a string representation of a lex boundary as a command argument. */ -function getLexBoundaryArg( - score: ScoreBoundary | ScoreBoundary, -): string { +function getLexBoundaryArg(score: Boundary | Boundary): string { if (typeof score === "string") { - // InfScoreBoundary + // InfBoundary return score; } @@ -1637,18 +1635,18 @@ function getLexBoundaryArg( /** Returns a string representation of a stream boundary as a command argument. */ function getStreamBoundaryArg( - score: ScoreBoundary | ScoreBoundary, + boundary: Boundary | Boundary, ): string { - if (typeof score === "string") { - // InfScoreBoundary - return score; + if (typeof boundary === "string") { + // InfBoundary + return boundary; } - if (score.isInclusive == false) { - return "(" + score.value.toString(); + if (boundary.isInclusive == false) { + return "(" + boundary.value.toString(); } - return score.value.toString(); + return boundary.value.toString(); } function createZRangeArgs( @@ -1704,8 +1702,8 @@ function createZRangeArgs( */ export function createZCount( key: string, - minScore: ScoreBoundary, - maxScore: ScoreBoundary, + minScore: Boundary, + maxScore: Boundary, ): command_request.Command { const args = [ key, @@ -1861,8 +1859,8 @@ export function createZRemRangeByRank( */ export function createZRemRangeByLex( key: string, - minLex: ScoreBoundary, - maxLex: ScoreBoundary, + minLex: Boundary, + maxLex: Boundary, ): command_request.Command { const args = [key, getLexBoundaryArg(minLex), getLexBoundaryArg(maxLex)]; return createCommand(RequestType.ZRemRangeByLex, args); @@ -1873,8 +1871,8 @@ export function createZRemRangeByLex( */ export function createZRemRangeByScore( key: string, - minScore: ScoreBoundary, - maxScore: ScoreBoundary, + minScore: Boundary, + maxScore: Boundary, ): command_request.Command { const args = [ key, @@ -1894,8 +1892,8 @@ export function createPersist(key: string): command_request.Command { */ export function createZLexCount( key: string, - minLex: ScoreBoundary, - maxLex: ScoreBoundary, + minLex: Boundary, + maxLex: Boundary, ): command_request.Command { const args = [key, getLexBoundaryArg(minLex), getLexBoundaryArg(maxLex)]; return createCommand(RequestType.ZLexCount, args); @@ -2040,6 +2038,25 @@ export function createXTrim( return createCommand(RequestType.XTrim, args); } +/** + * @internal + */ +export function createXRange( + key: string, + start: Boundary, + end: Boundary, + count?: number, +): command_request.Command { + const args = [key, getStreamBoundaryArg(start), getStreamBoundaryArg(end)]; + + if (count !== undefined) { + args.push("COUNT"); + args.push(count.toString()); + } + + return createCommand(RequestType.XRange, args); +} + /** * @internal */ @@ -2424,9 +2441,9 @@ export type StreamPendingOptions = { /** Filter pending entries by their idle time - in milliseconds */ minIdleTime?: number; /** Starting stream ID bound for range. */ - start: ScoreBoundary; + start: Boundary; /** Ending stream ID bound for range. */ - end: ScoreBoundary; + end: Boundary; /** Limit the number of messages returned. */ count: number; /** Filter pending entries by consumer. */ diff --git a/node/src/Transaction.ts b/node/src/Transaction.ts index 3da471684c..2d90524dd9 100644 --- a/node/src/Transaction.ts +++ b/node/src/Transaction.ts @@ -21,6 +21,7 @@ import { BitOffsetOptions, BitmapIndexType, BitwiseOperation, + Boundary, CoordOrigin, // eslint-disable-line @typescript-eslint/no-unused-vars ExpireOptions, FlushMode, @@ -46,7 +47,6 @@ import { RangeByLex, RangeByScore, ReturnTypeXinfoStream, // eslint-disable-line @typescript-eslint/no-unused-vars - ScoreBoundary, ScoreFilter, SearchOrigin, SetOptions, @@ -212,6 +212,7 @@ import { createXInfoStream, createXLen, createXPending, + createXRange, createXRead, createXTrim, createZAdd, @@ -1779,8 +1780,8 @@ export class BaseTransaction> { */ public zcount( key: string, - minScore: ScoreBoundary, - maxScore: ScoreBoundary, + minScore: Boundary, + maxScore: Boundary, ): T { return this.addAndReturn(createZCount(key, minScore, maxScore)); } @@ -2082,8 +2083,8 @@ export class BaseTransaction> { */ public zremRangeByLex( key: string, - minLex: ScoreBoundary, - maxLex: ScoreBoundary, + minLex: Boundary, + maxLex: Boundary, ): T { return this.addAndReturn(createZRemRangeByLex(key, minLex, maxLex)); } @@ -2101,8 +2102,8 @@ export class BaseTransaction> { */ public zremRangeByScore( key: string, - minScore: ScoreBoundary, - maxScore: ScoreBoundary, + minScore: Boundary, + maxScore: Boundary, ): T { return this.addAndReturn( createZRemRangeByScore(key, minScore, maxScore), @@ -2124,8 +2125,8 @@ export class BaseTransaction> { */ public zlexcount( key: string, - minLex: ScoreBoundary, - maxLex: ScoreBoundary, + minLex: Boundary, + maxLex: Boundary, ): T { return this.addAndReturn(createZLexCount(key, minLex, maxLex)); } @@ -2330,6 +2331,34 @@ export class BaseTransaction> { return this.addAndReturn(createTime()); } + /** + * Returns stream entries matching a given range of entry IDs. + * + * See https://valkey.io/commands/xrange for more details. + * + * @param key - The key of the stream. + * @param start - The starting stream entry ID bound for the range. + * - Use `value` to specify a stream entry ID. + * - Use `isInclusive: false` to specify an exclusive bounded stream entry ID. This is only available starting with Valkey version 6.2.0. + * - Use `InfBoundary.NegativeInfinity` to start with the minimum available ID. + * @param end - The ending stream ID bound for the range. + * - Use `value` to specify a stream entry ID. + * - Use `isInclusive: false` to specify an exclusive bounded stream entry ID. This is only available starting with Valkey version 6.2.0. + * - Use `InfBoundary.PositiveInfinity` to end with the maximum available ID. + * @param count - An optional argument specifying the maximum count of stream entries to return. + * If `count` is not provided, all stream entries in the range will be returned. + * + * Command Response - A map of stream entry ids, to an array of entries, or `null` if `count` is negative. + */ + public xrange( + key: string, + start: Boundary, + end: Boundary, + count?: number, + ): T { + return this.addAndReturn(createXRange(key, start, end, count)); + } + /** * Reads entries from the given streams. * See https://valkey.io/commands/xread/ for more details. diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts index 19228485c3..0936117791 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -29,7 +29,7 @@ import { GeospatialData, GlideClient, GlideClusterClient, - InfScoreBoundary, + InfBoundary, InfoOptions, InsertPosition, ListDirection, @@ -3675,8 +3675,8 @@ export function runBaseTests(config: { expect( await client.zcount( key1, - InfScoreBoundary.NegativeInfinity, - InfScoreBoundary.PositiveInfinity, + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, ), ).toEqual(3); expect( @@ -3694,28 +3694,20 @@ export function runBaseTests(config: { ), ).toEqual(2); expect( - await client.zcount( - key1, - InfScoreBoundary.NegativeInfinity, - { - value: 3, - }, - ), + await client.zcount(key1, InfBoundary.NegativeInfinity, { + value: 3, + }), ).toEqual(3); expect( - await client.zcount( - key1, - InfScoreBoundary.PositiveInfinity, - { - value: 3, - }, - ), + await client.zcount(key1, InfBoundary.PositiveInfinity, { + value: 3, + }), ).toEqual(0); expect( await client.zcount( "nonExistingKey", - InfScoreBoundary.NegativeInfinity, - InfScoreBoundary.PositiveInfinity, + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, ), ).toEqual(0); @@ -3723,8 +3715,8 @@ export function runBaseTests(config: { await expect( client.zcount( key2, - InfScoreBoundary.NegativeInfinity, - InfScoreBoundary.PositiveInfinity, + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, ), ).rejects.toThrow(); }, protocol); @@ -3779,14 +3771,14 @@ export function runBaseTests(config: { expect( await client.zrange(key, { - start: InfScoreBoundary.NegativeInfinity, + start: InfBoundary.NegativeInfinity, stop: { value: 3, isInclusive: false }, type: "byScore", }), ).toEqual(["one", "two"]); const result = await client.zrangeWithScores(key, { - start: InfScoreBoundary.NegativeInfinity, - stop: InfScoreBoundary.PositiveInfinity, + start: InfBoundary.NegativeInfinity, + stop: InfBoundary.PositiveInfinity, type: "byScore", }); @@ -3802,7 +3794,7 @@ export function runBaseTests(config: { key, { start: { value: 3, isInclusive: false }, - stop: InfScoreBoundary.NegativeInfinity, + stop: InfBoundary.NegativeInfinity, type: "byScore", }, true, @@ -3811,8 +3803,8 @@ export function runBaseTests(config: { expect( await client.zrange(key, { - start: InfScoreBoundary.NegativeInfinity, - stop: InfScoreBoundary.PositiveInfinity, + start: InfBoundary.NegativeInfinity, + stop: InfBoundary.PositiveInfinity, limit: { offset: 1, count: 2 }, type: "byScore", }), @@ -3822,7 +3814,7 @@ export function runBaseTests(config: { await client.zrange( key, { - start: InfScoreBoundary.NegativeInfinity, + start: InfBoundary.NegativeInfinity, stop: { value: 3, isInclusive: false }, type: "byScore", }, @@ -3832,7 +3824,7 @@ export function runBaseTests(config: { expect( await client.zrange(key, { - start: InfScoreBoundary.PositiveInfinity, + start: InfBoundary.PositiveInfinity, stop: { value: 3, isInclusive: false }, type: "byScore", }), @@ -3842,7 +3834,7 @@ export function runBaseTests(config: { await client.zrangeWithScores( key, { - start: InfScoreBoundary.NegativeInfinity, + start: InfBoundary.NegativeInfinity, stop: { value: 3, isInclusive: false }, type: "byScore", }, @@ -3852,7 +3844,7 @@ export function runBaseTests(config: { expect( await client.zrangeWithScores(key, { - start: InfScoreBoundary.PositiveInfinity, + start: InfBoundary.PositiveInfinity, stop: { value: 3, isInclusive: false }, type: "byScore", }), @@ -3872,7 +3864,7 @@ export function runBaseTests(config: { expect( await client.zrange(key, { - start: InfScoreBoundary.NegativeInfinity, + start: InfBoundary.NegativeInfinity, stop: { value: "c", isInclusive: false }, type: "byLex", }), @@ -3880,8 +3872,8 @@ export function runBaseTests(config: { expect( await client.zrange(key, { - start: InfScoreBoundary.NegativeInfinity, - stop: InfScoreBoundary.PositiveInfinity, + start: InfBoundary.NegativeInfinity, + stop: InfBoundary.PositiveInfinity, limit: { offset: 1, count: 2 }, type: "byLex", }), @@ -3892,7 +3884,7 @@ export function runBaseTests(config: { key, { start: { value: "c", isInclusive: false }, - stop: InfScoreBoundary.NegativeInfinity, + stop: InfBoundary.NegativeInfinity, type: "byLex", }, true, @@ -3903,7 +3895,7 @@ export function runBaseTests(config: { await client.zrange( key, { - start: InfScoreBoundary.NegativeInfinity, + start: InfBoundary.NegativeInfinity, stop: { value: "c", isInclusive: false }, type: "byLex", }, @@ -3913,7 +3905,7 @@ export function runBaseTests(config: { expect( await client.zrange(key, { - start: InfScoreBoundary.PositiveInfinity, + start: InfBoundary.PositiveInfinity, stop: { value: "c", isInclusive: false }, type: "byLex", }), @@ -3989,7 +3981,7 @@ export function runBaseTests(config: { expect( await client.zrangeStore(destkey, key, { - start: InfScoreBoundary.NegativeInfinity, + start: InfBoundary.NegativeInfinity, stop: { value: 3, isInclusive: false }, type: "byScore", }), @@ -4007,7 +3999,7 @@ export function runBaseTests(config: { key, { start: { value: 3, isInclusive: false }, - stop: InfScoreBoundary.NegativeInfinity, + stop: InfBoundary.NegativeInfinity, type: "byScore", }, true, @@ -4026,8 +4018,8 @@ export function runBaseTests(config: { expect( await client.zrangeStore(destkey, key, { - start: InfScoreBoundary.NegativeInfinity, - stop: InfScoreBoundary.PositiveInfinity, + start: InfBoundary.NegativeInfinity, + stop: InfBoundary.PositiveInfinity, limit: { offset: 1, count: 2 }, type: "byScore", }), @@ -4044,7 +4036,7 @@ export function runBaseTests(config: { destkey, key, { - start: InfScoreBoundary.NegativeInfinity, + start: InfBoundary.NegativeInfinity, stop: { value: 3, isInclusive: false }, type: "byScore", }, @@ -4054,7 +4046,7 @@ export function runBaseTests(config: { expect( await client.zrangeStore(destkey, key, { - start: InfScoreBoundary.PositiveInfinity, + start: InfBoundary.PositiveInfinity, stop: { value: 3, isInclusive: false }, type: "byScore", }), @@ -4076,7 +4068,7 @@ export function runBaseTests(config: { expect( await client.zrangeStore(destkey, key, { - start: InfScoreBoundary.NegativeInfinity, + start: InfBoundary.NegativeInfinity, stop: { value: "c", isInclusive: false }, type: "byLex", }), @@ -4090,8 +4082,8 @@ export function runBaseTests(config: { expect( await client.zrangeStore(destkey, key, { - start: InfScoreBoundary.NegativeInfinity, - stop: InfScoreBoundary.PositiveInfinity, + start: InfBoundary.NegativeInfinity, + stop: InfBoundary.PositiveInfinity, limit: { offset: 1, count: 2 }, type: "byLex", }), @@ -4109,7 +4101,7 @@ export function runBaseTests(config: { key, { start: { value: "c", isInclusive: false }, - stop: InfScoreBoundary.NegativeInfinity, + stop: InfBoundary.NegativeInfinity, type: "byLex", }, true, @@ -4131,7 +4123,7 @@ export function runBaseTests(config: { destkey, key, { - start: InfScoreBoundary.NegativeInfinity, + start: InfBoundary.NegativeInfinity, stop: { value: "c", isInclusive: false }, type: "byLex", }, @@ -4141,7 +4133,7 @@ export function runBaseTests(config: { expect( await client.zrangeStore(destkey, key, { - start: InfScoreBoundary.PositiveInfinity, + start: InfBoundary.PositiveInfinity, stop: { value: "c", isInclusive: false }, type: "byLex", }), @@ -4975,6 +4967,131 @@ export function runBaseTests(config: { config.timeout, ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `xrange test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster) => { + const key = uuidv4(); + const nonExistingKey = uuidv4(); + const stringKey = uuidv4(); + const streamId1 = "0-1"; + const streamId2 = "0-2"; + const streamId3 = "0-3"; + + expect( + await client.xadd(key, [["f1", "v1"]], { id: streamId1 }), + ).toEqual(streamId1); + expect( + await client.xadd(key, [["f2", "v2"]], { id: streamId2 }), + ).toEqual(streamId2); + expect(await client.xlen(key)).toEqual(2); + + // get everything from the stream + expect( + await client.xrange( + key, + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, + ), + ).toEqual({ + [streamId1]: [["f1", "v1"]], + [streamId2]: [["f2", "v2"]], + }); + + // returns empty mapping if + before - + expect( + await client.xrange( + key, + InfBoundary.PositiveInfinity, + InfBoundary.NegativeInfinity, + ), + ).toEqual({}); + + expect( + await client.xadd(key, [["f3", "v3"]], { id: streamId3 }), + ).toEqual(streamId3); + + // get the newest entry + if (!cluster.checkIfServerVersionLessThan("6.2.0")) { + expect( + await client.xrange( + key, + { isInclusive: false, value: streamId2 }, + { value: "5" }, + 1, + ), + ).toEqual({ [streamId3]: [["f3", "v3"]] }); + } + + // xrange against an emptied stream + expect( + await client.xdel(key, [streamId1, streamId2, streamId3]), + ).toEqual(3); + expect( + await client.xrange( + key, + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, + 10, + ), + ).toEqual({}); + + expect( + await client.xrange( + nonExistingKey, + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, + ), + ).toEqual({}); + + // count value < 1 returns null + expect( + await client.xrange( + key, + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, + 0, + ), + ).toEqual(null); + expect( + await client.xrange( + key, + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, + -1, + ), + ).toEqual(null); + + // key exists, but it is not a stream + expect(await client.set(stringKey, "foo")); + await expect( + client.xrange( + stringKey, + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, + ), + ).rejects.toThrow(RequestError); + + // invalid start bound + await expect( + client.xrange( + key, + { value: "not_a_stream_id" }, + InfBoundary.PositiveInfinity, + ), + ).rejects.toThrow(RequestError); + + // invalid end bound + await expect( + client.xrange(key, InfBoundary.NegativeInfinity, { + value: "not_a_stream_id", + }), + ).rejects.toThrow(RequestError); + }, protocol); + }, + config.timeout, + ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( `zremRangeByLex test_%p`, async (protocol) => { @@ -4996,7 +5113,7 @@ export function runBaseTests(config: { await client.zremRangeByLex( key, { value: "d" }, - InfScoreBoundary.PositiveInfinity, + InfBoundary.PositiveInfinity, ), ).toEqual(1); @@ -5005,15 +5122,15 @@ export function runBaseTests(config: { await client.zremRangeByLex( key, { value: "a" }, - InfScoreBoundary.NegativeInfinity, + InfBoundary.NegativeInfinity, ), ).toEqual(0); expect( await client.zremRangeByLex( "nonExistingKey", - InfScoreBoundary.NegativeInfinity, - InfScoreBoundary.PositiveInfinity, + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, ), ).toEqual(0); @@ -5022,8 +5139,8 @@ export function runBaseTests(config: { await expect( client.zremRangeByLex( stringKey, - InfScoreBoundary.NegativeInfinity, - InfScoreBoundary.PositiveInfinity, + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, ), ).rejects.toThrow(RequestError); }, protocol); @@ -5051,15 +5168,15 @@ export function runBaseTests(config: { await client.zremRangeByScore( key, { value: 1 }, - InfScoreBoundary.NegativeInfinity, + InfBoundary.NegativeInfinity, ), ).toEqual(0); expect( await client.zremRangeByScore( "nonExistingKey", - InfScoreBoundary.NegativeInfinity, - InfScoreBoundary.PositiveInfinity, + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, ), ).toEqual(0); }, protocol); @@ -5080,8 +5197,8 @@ export function runBaseTests(config: { expect( await client.zlexcount( key, - InfScoreBoundary.NegativeInfinity, - InfScoreBoundary.PositiveInfinity, + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, ), ).toEqual(3); @@ -5090,40 +5207,32 @@ export function runBaseTests(config: { await client.zlexcount( key, { value: "a", isInclusive: false }, - InfScoreBoundary.PositiveInfinity, + InfBoundary.PositiveInfinity, ), ).toEqual(2); // In range negative infinity to c (inclusive) expect( - await client.zlexcount( - key, - InfScoreBoundary.NegativeInfinity, - { - value: "c", - isInclusive: true, - }, - ), + await client.zlexcount(key, InfBoundary.NegativeInfinity, { + value: "c", + isInclusive: true, + }), ).toEqual(3); // Incorrect range start > end expect( - await client.zlexcount( - key, - InfScoreBoundary.PositiveInfinity, - { - value: "c", - isInclusive: true, - }, - ), + await client.zlexcount(key, InfBoundary.PositiveInfinity, { + value: "c", + isInclusive: true, + }), ).toEqual(0); // Non-existing key expect( await client.zlexcount( "non_existing_key", - InfScoreBoundary.NegativeInfinity, - InfScoreBoundary.PositiveInfinity, + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, ), ).toEqual(0); @@ -5132,8 +5241,8 @@ export function runBaseTests(config: { await expect( client.zlexcount( stringKey, - InfScoreBoundary.NegativeInfinity, - InfScoreBoundary.PositiveInfinity, + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, ), ).rejects.toThrow(RequestError); }, protocol); @@ -8003,8 +8112,8 @@ export function runBaseTests(config: { ]); const result = await client.xpendingWithOptions(key, group, { - start: InfScoreBoundary.NegativeInfinity, - end: InfScoreBoundary.PositiveInfinity, + start: InfBoundary.NegativeInfinity, + end: InfBoundary.PositiveInfinity, count: 1, minIdleTime: 42, }); diff --git a/node/tests/TestUtilities.ts b/node/tests/TestUtilities.ts index a55ec380e6..d90283399c 100644 --- a/node/tests/TestUtilities.ts +++ b/node/tests/TestUtilities.ts @@ -24,7 +24,7 @@ import { GeospatialData, GlideClient, GlideClusterClient, - InfScoreBoundary, + InfBoundary, InsertPosition, ListDirection, ProtocolVersion, @@ -987,22 +987,18 @@ export async function transactionTest( responseData.push(["zinterstore(key12, [key12, key13])", 2]); } - baseTransaction.zcount( - key8, - { value: 2 }, - InfScoreBoundary.PositiveInfinity, - ); + baseTransaction.zcount(key8, { value: 2 }, InfBoundary.PositiveInfinity); responseData.push([ - "zcount(key8, { value: 2 }, InfScoreBoundary.PositiveInfinity)", + "zcount(key8, { value: 2 }, InfBoundary.PositiveInfinity)", 4, ]); baseTransaction.zlexcount( key8, { value: "a" }, - InfScoreBoundary.PositiveInfinity, + InfBoundary.PositiveInfinity, ); responseData.push([ - 'zlexcount(key8, { value: "a" }, InfScoreBoundary.PositiveInfinity)', + 'zlexcount(key8, { value: "a" }, InfBoundary.PositiveInfinity)', 4, ]); baseTransaction.zpopmin(key8); @@ -1021,14 +1017,14 @@ export async function transactionTest( responseData.push(["zremRangeByRank(key8, 1, 1)", 1]); baseTransaction.zremRangeByScore( key8, - InfScoreBoundary.NegativeInfinity, - InfScoreBoundary.PositiveInfinity, + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, ); responseData.push(["zremRangeByScore(key8, -Inf, +Inf)", 1]); // key8 is now empty baseTransaction.zremRangeByLex( key8, - InfScoreBoundary.NegativeInfinity, - InfScoreBoundary.PositiveInfinity, + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, ); responseData.push(["zremRangeByLex(key8, -Inf, +Inf)", 0]); // key8 is already empty @@ -1074,6 +1070,8 @@ export async function transactionTest( ]); baseTransaction.xlen(key9); responseData.push(["xlen(key9)", 3]); + baseTransaction.xrange(key9, { value: "0-1" }, { value: "0-1" }); + responseData.push(["xrange(key9)", { "0-1": [["field", "value1"]] }]); baseTransaction.xread({ [key9]: "0-1" }); responseData.push([ 'xread({ [key9]: "0-1" })', @@ -1131,8 +1129,8 @@ export async function transactionTest( [1, "0-2", "0-2", [[consumer, "1"]]], ]); baseTransaction.xpendingWithOptions(key9, groupName1, { - start: InfScoreBoundary.NegativeInfinity, - end: InfScoreBoundary.PositiveInfinity, + start: InfBoundary.NegativeInfinity, + end: InfBoundary.PositiveInfinity, count: 10, }); responseData.push([