resplite 1.2.10 → 1.2.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -16
- package/package.json +1 -1
- package/scripts/benchmark-redis-vs-resplite.js +31 -0
- package/src/commands/registry.js +8 -2
- package/src/commands/zrank.js +18 -0
- package/src/commands/zrevrangebyscore.js +46 -0
- package/src/commands/zrevrank.js +18 -0
- package/src/engine/engine.js +28 -0
- package/src/storage/sqlite/zsets.js +61 -0
- package/test/integration/zsets.test.js +50 -0
package/README.md
CHANGED
|
@@ -515,21 +515,24 @@ A typical comparison is **Redis (for example, in Docker)** on one side and **RES
|
|
|
515
515
|
|
|
516
516
|
**Example results (Redis vs RESPLite, default pragma, 10k iterations):**
|
|
517
517
|
|
|
518
|
-
| Suite
|
|
519
|
-
|
|
520
|
-
| PING
|
|
521
|
-
| SET+GET
|
|
522
|
-
| MSET+MGET(10)
|
|
523
|
-
| INCR
|
|
524
|
-
| HSET+HGET
|
|
525
|
-
| HGETALL(50)
|
|
526
|
-
| HLEN(50)
|
|
527
|
-
| SADD+SMEMBERS
|
|
528
|
-
| LPUSH+LRANGE
|
|
529
|
-
| LREM
|
|
530
|
-
| ZADD+ZRANGE
|
|
531
|
-
|
|
|
532
|
-
|
|
|
518
|
+
| Suite | Redis (Docker) | RESPLite (default) |
|
|
519
|
+
|-------------------|----------------|--------------------|
|
|
520
|
+
| PING | 9.72K/s | 37.66K/s |
|
|
521
|
+
| SET+GET | 4.60K/s | 11.96K/s |
|
|
522
|
+
| MSET+MGET(10) | 4.38K/s | 5.71K/s |
|
|
523
|
+
| INCR | 9.76K/s | 19.15K/s |
|
|
524
|
+
| HSET+HGET | 4.42K/s | 11.71K/s |
|
|
525
|
+
| HGETALL(50) | 8.42K/s | 11.12K/s |
|
|
526
|
+
| HLEN(50) | 8.88K/s | 30.72K/s |
|
|
527
|
+
| SADD+SMEMBERS | 8.33K/s | 18.19K/s |
|
|
528
|
+
| LPUSH+LRANGE | 8.29K/s | 14.78K/s |
|
|
529
|
+
| LREM | 4.85K/s | 6.35K/s |
|
|
530
|
+
| ZADD+ZRANGE | 9.37K/s | 16.43K/s |
|
|
531
|
+
| ZADD+ZREVRANGE | 8.22K/s | 16.82K/s |
|
|
532
|
+
| ZRANK+ZREVRANK | 4.56K/s | 13.03K/s |
|
|
533
|
+
| ZREVRANGEBYSCORE | 8.88K/s | 16.88K/s |
|
|
534
|
+
| SET+DEL | 4.75K/s | 9.99K/s |
|
|
535
|
+
| FT.SEARCH | 8.39K/s | 8.81K/s |
|
|
533
536
|
|
|
534
537
|
To reproduce the benchmark, run `npm run benchmark -- --template default`. Numbers depend on host and whether Redis is native or in Docker.
|
|
535
538
|
|
|
@@ -545,7 +548,7 @@ To reproduce the benchmark, run `npm run benchmark -- --template default`. Numbe
|
|
|
545
548
|
| **Hashes** | HSET, HGET, HMGET, HGETALL, HDEL, HEXISTS, HINCRBY |
|
|
546
549
|
| **Sets** | SADD, SREM, SMEMBERS, SISMEMBER, SCARD |
|
|
547
550
|
| **Lists** | LPUSH, RPUSH, LLEN, LRANGE, LINDEX, LPOP, RPOP, BLPOP, BRPOP |
|
|
548
|
-
| **Sorted sets** | ZADD, ZREM, ZCARD, ZSCORE, ZRANGE, ZRANGEBYSCORE |
|
|
551
|
+
| **Sorted sets** | ZADD, ZREM, ZCARD, ZSCORE, ZRANGE, ZREVRANGE, ZRANGEBYSCORE, ZREVRANGEBYSCORE, ZRANK, ZREVRANK |
|
|
549
552
|
| **Search (FT.\*)** | FT.CREATE, FT.INFO, FT.ADD, FT.DEL, FT.SEARCH, FT.SUGADD, FT.SUGGET, FT.SUGDEL |
|
|
550
553
|
| **Introspection** | TYPE, OBJECT IDLETIME, SCAN, KEYS, MONITOR |
|
|
551
554
|
| **Admin** | SQLITE.INFO, CACHE.INFO, MEMORY.INFO |
|
package/package.json
CHANGED
|
@@ -258,6 +258,34 @@ async function benchZaddZrange(client, n) {
|
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
+
async function benchZaddZrevrange(client, n) {
|
|
262
|
+
const key = 'bm:zset:rev';
|
|
263
|
+
for (let i = 0; i < n; i++) {
|
|
264
|
+
await client.zAdd(key, { score: i, value: `m${i}` });
|
|
265
|
+
if (i % 10 === 0) await client.zRange(key, 0, 49, { REV: true });
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async function benchZrankZrevrank(client, n) {
|
|
270
|
+
const key = 'bm:zset:rank';
|
|
271
|
+
await client.del(key);
|
|
272
|
+
await client.zAdd(key, Array.from({ length: 100 }, (_, i) => ({ score: i, value: `m${i}` })));
|
|
273
|
+
for (let i = 0; i < n; i++) {
|
|
274
|
+
const member = `m${i % 100}`;
|
|
275
|
+
await client.zRank(key, member);
|
|
276
|
+
await client.zRevRank(key, member);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async function benchZrevrangebyscore(client, n) {
|
|
281
|
+
const key = 'bm:zset:byscore';
|
|
282
|
+
await client.del(key);
|
|
283
|
+
await client.zAdd(key, Array.from({ length: 100 }, (_, i) => ({ score: i, value: `m${i}` })));
|
|
284
|
+
for (let i = 0; i < n; i++) {
|
|
285
|
+
await client.sendCommand(['ZREVRANGEBYSCORE', key, '99', '0', 'LIMIT', '0', '20']);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
261
289
|
async function benchDel(client, n) {
|
|
262
290
|
for (let i = 0; i < n; i++) {
|
|
263
291
|
await client.set(`bm:del:${i}`, 'x');
|
|
@@ -311,6 +339,9 @@ const SUITES = [
|
|
|
311
339
|
{ name: 'LPUSH+LRANGE', fn: benchLpushLrange, iterScale: 1 },
|
|
312
340
|
{ name: 'LREM', fn: benchLrem, iterScale: 1 },
|
|
313
341
|
{ name: 'ZADD+ZRANGE', fn: benchZaddZrange, iterScale: 1 },
|
|
342
|
+
{ name: 'ZADD+ZREVRANGE', fn: benchZaddZrevrange, iterScale: 1 },
|
|
343
|
+
{ name: 'ZRANK+ZREVRANK', fn: benchZrankZrevrank, iterScale: 1 },
|
|
344
|
+
{ name: 'ZREVRANGEBYSCORE', fn: benchZrevrangebyscore, iterScale: 1 },
|
|
314
345
|
{ name: 'SET+DEL', fn: benchDel, iterScale: 1 },
|
|
315
346
|
{ name: 'FT.SEARCH', fn: benchFtSearch, iterScale: 1 },
|
|
316
347
|
];
|
package/src/commands/registry.js
CHANGED
|
@@ -53,8 +53,11 @@ import * as zrem from './zrem.js';
|
|
|
53
53
|
import * as zcard from './zcard.js';
|
|
54
54
|
import * as zscore from './zscore.js';
|
|
55
55
|
import * as zrange from './zrange.js';
|
|
56
|
-
import * as zrevrange from './zrevrange.js';
|
|
57
56
|
import * as zrangebyscore from './zrangebyscore.js';
|
|
57
|
+
import * as zrevrange from './zrevrange.js';
|
|
58
|
+
import * as zrevrangebyscore from './zrevrangebyscore.js';
|
|
59
|
+
import * as zrevrank from './zrevrank.js';
|
|
60
|
+
import * as zrank from './zrank.js';
|
|
58
61
|
import * as sqliteInfo from './sqlite-info.js';
|
|
59
62
|
import * as cacheInfo from './cache-info.js';
|
|
60
63
|
import * as memoryInfo from './memory-info.js';
|
|
@@ -119,8 +122,11 @@ const HANDLERS = new Map([
|
|
|
119
122
|
['ZCARD', (e, a) => zcard.handleZcard(e, a)],
|
|
120
123
|
['ZSCORE', (e, a) => zscore.handleZscore(e, a)],
|
|
121
124
|
['ZRANGE', (e, a) => zrange.handleZrange(e, a)],
|
|
122
|
-
['ZREVRANGE', (e, a) => zrevrange.handleZrevrange(e, a)],
|
|
123
125
|
['ZRANGEBYSCORE', (e, a) => zrangebyscore.handleZrangebyscore(e, a)],
|
|
126
|
+
['ZREVRANGE', (e, a) => zrevrange.handleZrevrange(e, a)],
|
|
127
|
+
['ZREVRANGEBYSCORE', (e, a) => zrevrangebyscore.handleZrevrangebyscore(e, a)],
|
|
128
|
+
['ZREVRANK', (e, a) => zrevrank.handleZrevrank(e, a)],
|
|
129
|
+
['ZRANK', (e, a) => zrank.handleZrank(e, a)],
|
|
124
130
|
['SQLITE.INFO', (e, a) => sqliteInfo.handleSqliteInfo(e, a)],
|
|
125
131
|
['CACHE.INFO', (e, a) => cacheInfo.handleCacheInfo(e, a)],
|
|
126
132
|
['MEMORY.INFO', (e, a) => memoryInfo.handleMemoryInfo(e, a)],
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZRANK key member
|
|
3
|
+
* Returns the 0-based rank of member in the sorted set (low to high score).
|
|
4
|
+
* Returns null if key does not exist or member is not in the set.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export function handleZrank(engine, args) {
|
|
8
|
+
if (!args || args.length < 2) {
|
|
9
|
+
return { error: 'ERR wrong number of arguments for \'ZRANK\' command' };
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
const rank = engine.zrank(args[0], args[1]);
|
|
13
|
+
return rank;
|
|
14
|
+
} catch (e) {
|
|
15
|
+
const msg = e && e.message ? e.message : String(e);
|
|
16
|
+
return { error: msg.startsWith('ERR ') ? msg : msg.startsWith('WRONGTYPE') ? msg : 'ERR ' + msg };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
|
|
3
|
+
* Same as ZRANGEBYSCORE but ordered from highest to lowest score.
|
|
4
|
+
* Note: Redis uses max min (first score is upper bound, second is lower bound).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export function handleZrevrangebyscore(engine, args) {
|
|
8
|
+
if (!args || args.length < 3) {
|
|
9
|
+
return { error: 'ERR wrong number of arguments for \'ZREVRANGEBYSCORE\' command' };
|
|
10
|
+
}
|
|
11
|
+
let withScores = false;
|
|
12
|
+
let limitOffset = null;
|
|
13
|
+
let limitCount = null;
|
|
14
|
+
const raw = args.slice(3);
|
|
15
|
+
for (let i = 0; i < raw.length; i++) {
|
|
16
|
+
const a = (Buffer.isBuffer(raw[i]) ? raw[i].toString('utf8') : String(raw[i])).toUpperCase();
|
|
17
|
+
if (a === 'WITHSCORES') {
|
|
18
|
+
withScores = true;
|
|
19
|
+
} else if (a === 'LIMIT' && i + 2 < raw.length) {
|
|
20
|
+
const off = parseInt(String(raw[i + 1]), 10);
|
|
21
|
+
const cnt = parseInt(String(raw[i + 2]), 10);
|
|
22
|
+
if (!Number.isNaN(off) && !Number.isNaN(cnt)) {
|
|
23
|
+
limitOffset = off;
|
|
24
|
+
limitCount = cnt;
|
|
25
|
+
}
|
|
26
|
+
i += 2;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const max = parseFloat(String(args[1]));
|
|
31
|
+
const min = parseFloat(String(args[2]));
|
|
32
|
+
if (Number.isNaN(max) || Number.isNaN(min)) {
|
|
33
|
+
return { error: 'ERR value is not a valid float' };
|
|
34
|
+
}
|
|
35
|
+
const options = { withScores };
|
|
36
|
+
if (limitOffset != null && limitCount != null) {
|
|
37
|
+
options.offset = limitOffset;
|
|
38
|
+
options.limit = limitCount;
|
|
39
|
+
}
|
|
40
|
+
const arr = engine.zrevrangebyscore(args[0], max, min, options);
|
|
41
|
+
return arr;
|
|
42
|
+
} catch (e) {
|
|
43
|
+
const msg = e && e.message ? e.message : String(e);
|
|
44
|
+
return { error: msg.startsWith('ERR ') ? msg : msg.startsWith('WRONGTYPE') ? msg : 'ERR ' + msg };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZREVRANK key member
|
|
3
|
+
* Returns the 0-based rank of member in the sorted set (high to low score).
|
|
4
|
+
* Returns null if key does not exist or member is not in the set.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export function handleZrevrank(engine, args) {
|
|
8
|
+
if (!args || args.length < 2) {
|
|
9
|
+
return { error: 'ERR wrong number of arguments for \'ZREVRANK\' command' };
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
const rank = engine.zrevrank(args[0], args[1]);
|
|
13
|
+
return rank;
|
|
14
|
+
} catch (e) {
|
|
15
|
+
const msg = e && e.message ? e.message : String(e);
|
|
16
|
+
return { error: msg.startsWith('ERR ') ? msg : msg.startsWith('WRONGTYPE') ? msg : 'ERR ' + msg };
|
|
17
|
+
}
|
|
18
|
+
}
|
package/src/engine/engine.js
CHANGED
|
@@ -395,6 +395,22 @@ export function createEngine(opts = {}) {
|
|
|
395
395
|
return zsets.rangeByRankReverse(k, start, stop, { withScores: options.withScores });
|
|
396
396
|
},
|
|
397
397
|
|
|
398
|
+
zrevrank(key, member) {
|
|
399
|
+
const k = asKey(key);
|
|
400
|
+
const meta = getKeyMeta(key);
|
|
401
|
+
if (!meta) return null;
|
|
402
|
+
expectZset(meta);
|
|
403
|
+
return zsets.rankReverse(k, asKey(member));
|
|
404
|
+
},
|
|
405
|
+
|
|
406
|
+
zrank(key, member) {
|
|
407
|
+
const k = asKey(key);
|
|
408
|
+
const meta = getKeyMeta(key);
|
|
409
|
+
if (!meta) return null;
|
|
410
|
+
expectZset(meta);
|
|
411
|
+
return zsets.rank(k, asKey(member));
|
|
412
|
+
},
|
|
413
|
+
|
|
398
414
|
zrangebyscore(key, min, max, options = {}) {
|
|
399
415
|
const k = asKey(key);
|
|
400
416
|
const meta = getKeyMeta(key);
|
|
@@ -407,6 +423,18 @@ export function createEngine(opts = {}) {
|
|
|
407
423
|
});
|
|
408
424
|
},
|
|
409
425
|
|
|
426
|
+
zrevrangebyscore(key, max, min, options = {}) {
|
|
427
|
+
const k = asKey(key);
|
|
428
|
+
const meta = getKeyMeta(key);
|
|
429
|
+
if (!meta) return [];
|
|
430
|
+
expectZset(meta);
|
|
431
|
+
return zsets.rangeByScoreReverse(k, max, min, {
|
|
432
|
+
withScores: options.withScores,
|
|
433
|
+
offset: options.offset ?? 0,
|
|
434
|
+
limit: options.limit,
|
|
435
|
+
});
|
|
436
|
+
},
|
|
437
|
+
|
|
410
438
|
type(key) {
|
|
411
439
|
const meta = getKeyMeta(key);
|
|
412
440
|
return typeName(meta);
|
|
@@ -48,6 +48,20 @@ export function createZsetsStorage(db, keys) {
|
|
|
48
48
|
ORDER BY score ASC, member ASC
|
|
49
49
|
LIMIT ? OFFSET ?`
|
|
50
50
|
);
|
|
51
|
+
const rankReverseStmt = db.prepare(
|
|
52
|
+
`SELECT COUNT(*) AS n FROM redis_zsets
|
|
53
|
+
WHERE key = ? AND (score > ? OR (score = ? AND member > ?))`
|
|
54
|
+
);
|
|
55
|
+
const rankStmt = db.prepare(
|
|
56
|
+
`SELECT COUNT(*) AS n FROM redis_zsets
|
|
57
|
+
WHERE key = ? AND (score < ? OR (score = ? AND member < ?))`
|
|
58
|
+
);
|
|
59
|
+
const rangeByScoreReverseStmt = db.prepare(
|
|
60
|
+
`SELECT member, score FROM redis_zsets
|
|
61
|
+
WHERE key = ? AND score <= ? AND score >= ?
|
|
62
|
+
ORDER BY score DESC, member DESC
|
|
63
|
+
LIMIT ? OFFSET ?`
|
|
64
|
+
);
|
|
51
65
|
|
|
52
66
|
return {
|
|
53
67
|
/**
|
|
@@ -195,5 +209,52 @@ export function createZsetsStorage(db, keys) {
|
|
|
195
209
|
}
|
|
196
210
|
return out;
|
|
197
211
|
},
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Rank of member in reverse order (ZREVRANK). Rank 0 = highest score.
|
|
215
|
+
* Returns null if key does not exist or member not in set.
|
|
216
|
+
* @param {Buffer} key
|
|
217
|
+
* @param {Buffer} member
|
|
218
|
+
* @returns {number | null} 0-based rank or null
|
|
219
|
+
*/
|
|
220
|
+
rankReverse(key, member) {
|
|
221
|
+
const scoreRow = scoreStmt.get(key, member);
|
|
222
|
+
if (scoreRow == null) return null;
|
|
223
|
+
const row = rankReverseStmt.get(key, scoreRow.score, scoreRow.score, member);
|
|
224
|
+
return row ? row.n : 0;
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Rank of member in ascending order (ZRANK). Rank 0 = lowest score.
|
|
229
|
+
* Returns null if key does not exist or member not in set.
|
|
230
|
+
* @param {Buffer} key
|
|
231
|
+
* @param {Buffer} member
|
|
232
|
+
* @returns {number | null} 0-based rank or null
|
|
233
|
+
*/
|
|
234
|
+
rank(key, member) {
|
|
235
|
+
const scoreRow = scoreStmt.get(key, member);
|
|
236
|
+
if (scoreRow == null) return null;
|
|
237
|
+
const row = rankStmt.get(key, scoreRow.score, scoreRow.score, member);
|
|
238
|
+
return row ? row.n : 0;
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Range by score in reverse order (ZREVRANGEBYSCORE). max/min inclusive, order score DESC, member DESC.
|
|
243
|
+
* @param {Buffer} key
|
|
244
|
+
* @param {number} max
|
|
245
|
+
* @param {number} min
|
|
246
|
+
* @param {{ withScores?: boolean, limit?: number, offset?: number }} options
|
|
247
|
+
*/
|
|
248
|
+
rangeByScoreReverse(key, max, min, options = {}) {
|
|
249
|
+
const limit = options.limit ?? -1;
|
|
250
|
+
const offset = options.offset ?? 0;
|
|
251
|
+
const rows = rangeByScoreReverseStmt.all(key, max, min, limit < 0 ? 1e9 : limit, offset);
|
|
252
|
+
if (!options.withScores) return rows.map((r) => r.member);
|
|
253
|
+
const out = [];
|
|
254
|
+
for (const r of rows) {
|
|
255
|
+
out.push(r.member, formatScore(r.score));
|
|
256
|
+
}
|
|
257
|
+
return out;
|
|
258
|
+
},
|
|
198
259
|
};
|
|
199
260
|
}
|
|
@@ -69,6 +69,56 @@ describe('ZSET integration', () => {
|
|
|
69
69
|
assert.equal(withArr[3].toString('utf8'), '2');
|
|
70
70
|
});
|
|
71
71
|
|
|
72
|
+
it('ZREVRANK returns 0-based rank high-to-low and nil when missing', async () => {
|
|
73
|
+
await sendCommand(port, argv('ZADD', 'zrevrank', '1', 'a', '2', 'b', '3', 'c'));
|
|
74
|
+
const rankC = await sendCommand(port, argv('ZREVRANK', 'zrevrank', 'c'));
|
|
75
|
+
assert.equal(tryParseValue(rankC, 0).value, 0);
|
|
76
|
+
const rankB = await sendCommand(port, argv('ZREVRANK', 'zrevrank', 'b'));
|
|
77
|
+
assert.equal(tryParseValue(rankB, 0).value, 1);
|
|
78
|
+
const rankA = await sendCommand(port, argv('ZREVRANK', 'zrevrank', 'a'));
|
|
79
|
+
assert.equal(tryParseValue(rankA, 0).value, 2);
|
|
80
|
+
|
|
81
|
+
const noMember = await sendCommand(port, argv('ZREVRANK', 'zrevrank', 'x'));
|
|
82
|
+
assert.ok(noMember.toString('ascii').startsWith('$-1'));
|
|
83
|
+
const noKey = await sendCommand(port, argv('ZREVRANK', 'nokey', 'a'));
|
|
84
|
+
assert.ok(noKey.toString('ascii').startsWith('$-1'));
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('ZRANK returns 0-based rank low-to-high and nil when missing', async () => {
|
|
88
|
+
await sendCommand(port, argv('ZADD', 'zrank', '1', 'a', '2', 'b', '3', 'c'));
|
|
89
|
+
const rankA = await sendCommand(port, argv('ZRANK', 'zrank', 'a'));
|
|
90
|
+
assert.equal(tryParseValue(rankA, 0).value, 0);
|
|
91
|
+
const rankB = await sendCommand(port, argv('ZRANK', 'zrank', 'b'));
|
|
92
|
+
assert.equal(tryParseValue(rankB, 0).value, 1);
|
|
93
|
+
const rankC = await sendCommand(port, argv('ZRANK', 'zrank', 'c'));
|
|
94
|
+
assert.equal(tryParseValue(rankC, 0).value, 2);
|
|
95
|
+
|
|
96
|
+
const noMember = await sendCommand(port, argv('ZRANK', 'zrank', 'x'));
|
|
97
|
+
assert.ok(noMember.toString('ascii').startsWith('$-1'));
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('ZREVRANGEBYSCORE returns score range high-to-low and supports WITHSCORES and LIMIT', async () => {
|
|
101
|
+
await sendCommand(port, argv('ZADD', 'zrevscore', '1', 'a', '2', 'b', '3', 'c', '4', 'd'));
|
|
102
|
+
const rev = await sendCommand(port, argv('ZREVRANGEBYSCORE', 'zrevscore', '4', '2'));
|
|
103
|
+
const arr = tryParseValue(rev, 0).value;
|
|
104
|
+
assert.equal(arr.length, 3);
|
|
105
|
+
assert.equal(arr[0].toString('utf8'), 'd');
|
|
106
|
+
assert.equal(arr[1].toString('utf8'), 'c');
|
|
107
|
+
assert.equal(arr[2].toString('utf8'), 'b');
|
|
108
|
+
|
|
109
|
+
const withScores = await sendCommand(port, argv('ZREVRANGEBYSCORE', 'zrevscore', '10', '0', 'WITHSCORES'));
|
|
110
|
+
const withArr = tryParseValue(withScores, 0).value;
|
|
111
|
+
assert.equal(withArr.length, 8);
|
|
112
|
+
assert.equal(withArr[0].toString('utf8'), 'd');
|
|
113
|
+
assert.equal(withArr[1].toString('utf8'), '4');
|
|
114
|
+
|
|
115
|
+
const limitReply = await sendCommand(port, argv('ZREVRANGEBYSCORE', 'zrevscore', '10', '0', 'LIMIT', '1', '2'));
|
|
116
|
+
const limited = tryParseValue(limitReply, 0).value;
|
|
117
|
+
assert.equal(limited.length, 2);
|
|
118
|
+
assert.equal(limited[0].toString('utf8'), 'c');
|
|
119
|
+
assert.equal(limited[1].toString('utf8'), 'b');
|
|
120
|
+
});
|
|
121
|
+
|
|
72
122
|
it('ZRANGE negative indices and start > stop', async () => {
|
|
73
123
|
await sendCommand(port, argv('ZADD', 'z4', '1', 'a', '2', 'b', '3', 'c'));
|
|
74
124
|
const last = await sendCommand(port, argv('ZRANGE', 'z4', '-1', '-1'));
|