Skip to content

Commit b77ba02

Browse files
committed
feat(command): implement zRangeWithScores command with options for score range, reverse order, and pagination
1 parent ec413cd commit b77ba02

File tree

4 files changed

+218
-6
lines changed

4 files changed

+218
-6
lines changed

CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changes Logs
22

3+
## v3.0.6
4+
5+
- feat(command): implement zRangeWithScores command with options for score range, reverse order, and pagination.
6+
37
## v3.0.5
48

59
- fix(command): fixed optional step for `INCR-like` commands.

src/lib/Commands.ts

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2177,11 +2177,27 @@ export const COMMANDS: Record<keyof C.ICommandAPIs, ICommand> = {
21772177
* @see https://redis.io/docs/latest/commands/zrange
21782178
*/
21792179
'zRangeWithScores': {
2180-
prepare: (key: string, start: number, stop: number) => {
2180+
prepare: (key: string, start: number, stop: number, options?: C.IZRangeOptions) => {
2181+
2182+
const args: Array<string | number> = [key, start, stop];
2183+
2184+
if (options?.by) {
2185+
args.push('BY' + options.by);
2186+
}
2187+
2188+
if (options?.rev) {
2189+
args.push('REV');
2190+
}
2191+
2192+
if (options?.offset !== undefined && options?.count !== undefined) {
2193+
args.push('LIMIT', options.offset, options.count);
2194+
}
2195+
2196+
args.push('WITHSCORES');
21812197

21822198
return {
21832199
'cmd': 'ZRANGE',
2184-
'args': [key, start, stop, 'WITHSCORES']
2200+
'args': args
21852201
};
21862202
},
21872203
process: (items: Array<[number, Buffer]>): Array<{ member: string; score: number; }> => {
@@ -2205,11 +2221,27 @@ export const COMMANDS: Record<keyof C.ICommandAPIs, ICommand> = {
22052221
* @see https://redis.io/docs/latest/commands/zrange
22062222
*/
22072223
'zRangeWithScores$': {
2208-
prepare: (key: string, start: number, stop: number) => {
2224+
prepare: (key: string, start: number, stop: number, options?: C.IZRangeOptions) => {
2225+
2226+
const args: Array<string | number> = [key, start, stop];
2227+
2228+
if (options?.by) {
2229+
args.push('BY' + options.by);
2230+
}
2231+
2232+
if (options?.rev) {
2233+
args.push('REV');
2234+
}
2235+
2236+
if (options?.offset !== undefined && options?.count !== undefined) {
2237+
args.push('LIMIT', options.offset, options.count);
2238+
}
2239+
2240+
args.push('WITHSCORES');
22092241

22102242
return {
22112243
'cmd': 'ZRANGE',
2212-
'args': [key, start, stop, 'WITHSCORES']
2244+
'args': args
22132245
};
22142246
},
22152247
process: (items: Array<[number, Buffer]>): Array<{ member: Buffer; score: number; }> => {

src/lib/Common.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,36 @@ export interface IScanResult<T> {
331331
items: T[];
332332
}
333333

334+
/**
335+
* Options for ZRANGE command.
336+
* @see https://redis.io/docs/latest/commands/zrange
337+
*/
338+
export interface IZRangeOptions {
339+
340+
/**
341+
* Range query type.
342+
*
343+
* - SCORE: Query by score range
344+
* - LEX: Query by lexicographical range
345+
*/
346+
'by'?: 'SCORE' | 'LEX';
347+
348+
/**
349+
* Whether to return results in reverse order.
350+
*/
351+
'rev'?: boolean;
352+
353+
/**
354+
* Pagination offset. Must be used together with count.
355+
*/
356+
'offset'?: number;
357+
358+
/**
359+
* Pagination count. Must be used together with offset.
360+
*/
361+
'count'?: number;
362+
}
363+
334364
export interface ICommandAPIs {
335365

336366
/**
@@ -1298,13 +1328,13 @@ export interface ICommandAPIs {
12981328
* Command: zRange
12991329
* @see https://redis.io/docs/latest/commands/zrange
13001330
*/
1301-
zRangeWithScores(key: string, start: number, stop: number): Promise<Array<{ member: string; score: number; }>>;
1331+
zRangeWithScores(key: string, start: number, stop: number, options?: IZRangeOptions): Promise<Array<{ member: string; score: number; }>>;
13021332

13031333
/**
13041334
* Command: zRange
13051335
* @see https://redis.io/docs/latest/commands/zrange
13061336
*/
1307-
zRangeWithScores$(key: string, start: number, stop: number): Promise<Array<{ member: Buffer; score: number; }>>;
1337+
zRangeWithScores$(key: string, start: number, stop: number, options?: IZRangeOptions): Promise<Array<{ member: Buffer; score: number; }>>;
13081338

13091339
/**
13101340
* Command: pfAdd

src/test/commands/zrange.ts

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import { test } from 'node:test';
2+
import * as Assert from 'node:assert';
3+
import { cmdCli } from '../common';
4+
5+
const TEST_KEY_PREFIX = 'test_zrange_';
6+
7+
test('Command For zRangeWithScores', async (t) => {
8+
9+
await cmdCli.del([...await cmdCli.keys(`${TEST_KEY_PREFIX}*`), 'any']);
10+
11+
await t.test('Add multiple members to sorted set', async () => {
12+
13+
Assert.strictEqual(await cmdCli.zAdd(`${TEST_KEY_PREFIX}myset`, 1, 'one'), true);
14+
Assert.strictEqual(await cmdCli.zAdd(`${TEST_KEY_PREFIX}myset`, 2, 'two'), true);
15+
Assert.strictEqual(await cmdCli.zAdd(`${TEST_KEY_PREFIX}myset`, 3, 'three'), true);
16+
Assert.strictEqual(await cmdCli.zAdd(`${TEST_KEY_PREFIX}myset`, 4, 'four'), true);
17+
Assert.strictEqual(await cmdCli.zAdd(`${TEST_KEY_PREFIX}myset`, 5, 'five'), true);
18+
19+
});
20+
21+
await t.test('Get range by index with scores', async () => {
22+
23+
const result = await cmdCli.zRangeWithScores(`${TEST_KEY_PREFIX}myset`, 0, 2);
24+
25+
Assert.deepEqual(result, [
26+
{ 'member': 'one', 'score': 1 },
27+
{ 'member': 'two', 'score': 2 },
28+
{ 'member': 'three', 'score': 3 }
29+
]);
30+
31+
});
32+
33+
await t.test('Get range by index with scores in reverse order', async () => {
34+
35+
const result = await cmdCli.zRangeWithScores(`${TEST_KEY_PREFIX}myset`, 0, 2, { 'rev': true });
36+
37+
Assert.deepEqual(result, [
38+
{ 'member': 'five', 'score': 5 },
39+
{ 'member': 'four', 'score': 4 },
40+
{ 'member': 'three', 'score': 3 }
41+
]);
42+
43+
});
44+
45+
await t.test('Get range by score', async () => {
46+
47+
const result = await cmdCli.zRangeWithScores(`${TEST_KEY_PREFIX}myset`, 2, 4, { 'by': 'SCORE' });
48+
49+
Assert.deepEqual(result, [
50+
{ 'member': 'two', 'score': 2 },
51+
{ 'member': 'three', 'score': 3 },
52+
{ 'member': 'four', 'score': 4 }
53+
]);
54+
55+
});
56+
57+
await t.test('Get range by score with LIMIT', async () => {
58+
59+
const result = await cmdCli.zRangeWithScores(`${TEST_KEY_PREFIX}myset`, 1, 5, {
60+
'by': 'SCORE',
61+
'offset': 1,
62+
'count': 2
63+
});
64+
65+
Assert.deepEqual(result, [
66+
{ 'member': 'two', 'score': 2 },
67+
{ 'member': 'three', 'score': 3 }
68+
]);
69+
70+
});
71+
72+
await t.test('Get range by score in reverse order with LIMIT', async () => {
73+
74+
const result = await cmdCli.zRangeWithScores(`${TEST_KEY_PREFIX}myset`, 5, 1, {
75+
'by': 'SCORE',
76+
'rev': true,
77+
'offset': 0,
78+
'count': 2
79+
});
80+
81+
Assert.deepEqual(result, [
82+
{ 'member': 'five', 'score': 5 },
83+
{ 'member': 'four', 'score': 4 }
84+
]);
85+
86+
});
87+
88+
await t.test('Get range with negative indices', async () => {
89+
90+
const result = await cmdCli.zRangeWithScores(`${TEST_KEY_PREFIX}myset`, -3, -1);
91+
92+
Assert.deepEqual(result, [
93+
{ 'member': 'three', 'score': 3 },
94+
{ 'member': 'four', 'score': 4 },
95+
{ 'member': 'five', 'score': 5 }
96+
]);
97+
98+
});
99+
100+
await t.test('Get all members with scores', async () => {
101+
102+
const result = await cmdCli.zRangeWithScores(`${TEST_KEY_PREFIX}myset`, 0, -1);
103+
104+
Assert.deepEqual(result, [
105+
{ 'member': 'one', 'score': 1 },
106+
{ 'member': 'two', 'score': 2 },
107+
{ 'member': 'three', 'score': 3 },
108+
{ 'member': 'four', 'score': 4 },
109+
{ 'member': 'five', 'score': 5 }
110+
]);
111+
112+
});
113+
114+
await t.test('Get range with Buffer version', async () => {
115+
116+
const result = await cmdCli.zRangeWithScores$(`${TEST_KEY_PREFIX}myset`, 0, 1);
117+
118+
Assert.strictEqual(result.length, 2);
119+
Assert.strictEqual(result[0].member.toString(), 'one');
120+
Assert.strictEqual(result[0].score, 1);
121+
Assert.strictEqual(result[1].member.toString(), 'two');
122+
Assert.strictEqual(result[1].score, 2);
123+
124+
});
125+
126+
await t.test('Get empty range', async () => {
127+
128+
const result = await cmdCli.zRangeWithScores(`${TEST_KEY_PREFIX}myset`, 10, 20);
129+
130+
Assert.deepEqual(result, []);
131+
132+
});
133+
134+
await t.test('Clean up test data', async () => {
135+
136+
const deleted = await cmdCli.del(`${TEST_KEY_PREFIX}myset`);
137+
Assert.strictEqual(deleted, 1);
138+
139+
});
140+
141+
});
142+
143+
test.after(async () => {
144+
145+
await cmdCli.close();
146+
});

0 commit comments

Comments
 (0)