UNPKG

19.8 kBJavaScriptView Raw
1/* eslint complexity: "off", no-continue: "off" */
2
3var helpers = require("./helpers.js");
4var Item = require("./item.js");
5
6/**
7
8 *** NOT IMPLEMENTED ***
9
10 ZLEXCOUNT key min max
11 Count the number of members in a sorted set between a given lexicographical range
12
13 ZRANGEBYLEX key min max [LIMIT offset count]
14 Return a range of members in a sorted set, by lexicographical range
15
16 ZREVRANGEBYLEX key max min [LIMIT offset count]
17 Return a range of members in a sorted set, by lexicographical range, ordered from higher to lower strings.
18
19 ZREMRANGEBYLEX key min max
20 Remove all members in a sorted set between the given lexicographical range
21
22 ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
23 Add multiple sorted sets and store the resulting sorted set in a new key
24
25 ZSCAN key cursor [MATCH pattern] [COUNT count]
26 Incrementally iterate sorted sets elements and associated scores
27
28
29 Also: ZUNIONSTORE / ZINTERSTORE is only partially implemented.
30
31*/
32
33var MAX_SCORE_VALUE = 9007199254740992;
34var MIN_SCORE_VALUE = -MAX_SCORE_VALUE;
35
36var mockCallback = helpers.mockCallback;
37
38var validKeyType = function(mockInstance, key, callback) {
39 return helpers.validKeyType(mockInstance, key, 'zset', callback)
40}
41
42var initKey = function(mockInstance, key) {
43 return helpers.initKey(mockInstance, key, Item.createSortedSet);
44}
45
46// delimiter for lexicographically sorting by score & member
47var rankedDelimiter = '#';
48
49/*
50Returns a sorted set of all the score+members for a key
51*/
52var getRankedList = function(mockInstance, key) {
53 // returns a ranked list of items (k)
54 var items = [];
55 for (var member in mockInstance.storage[key].value) {
56 var score = parseFloat(mockInstance.storage[key].value[member]);
57 items.push([score, score + rankedDelimiter + member]);
58 }
59
60 //first sort by score then alphabetically
61 items.sort(
62 function (a, b) {
63 return a[0] - b[0] || a[1].localeCompare(b[1]);
64 });
65
66 //return the concatinated values
67 return items.map(function (value, index) {
68 return value[1];
69 });
70}
71
72/*
73getRank (zrank & zrevrank)
74*/
75var getRank = function(mockInstance, key, member, callback, reversed) {
76
77 var len = arguments.length;
78 if (len <= 3) {
79 return
80 }
81 if (callback == undefined) {
82 callback = mockCallback;
83 }
84 if (!validKeyType(mockInstance, key, callback)) {
85 return
86 }
87 initKey(mockInstance, key);
88 member = Item._stringify(member);
89 var rank = null;
90 var ranked = getRankedList(mockInstance, key);
91
92 // this is for zrevrank
93 if (reversed) {
94 ranked.reverse();
95 }
96
97 for (var i=0, parts, s, m; i < ranked.length; i++) {
98 parts = ranked[i].split(rankedDelimiter);
99 s = parts[0];
100 m = parts.slice(1).join(rankedDelimiter);
101 if (m === member) {
102 rank = i;
103 break;
104 }
105 }
106 mockInstance._callCallback(callback, null, rank);
107
108}
109
110/*
111getRange (zrange & zrevrange)
112*/
113var getRange = function(mockInstance, key, start, stop, withscores, callback, reversed) {
114 var len = arguments.length;
115 if (len < 4) {
116 return
117 }
118 if ('function' === typeof withscores) {
119 callback = withscores;
120 withscores = undefined;
121 }
122 if (callback == undefined) {
123 callback = mockCallback;
124 }
125 if (!validKeyType(mockInstance, key, callback)) {
126 return
127 }
128
129 initKey(mockInstance, key);
130 var ranked = getRankedList(mockInstance, key);
131
132 // this is for zrevrange
133 if (reversed) {
134 ranked.reverse();
135 }
136
137 // convert to string so we can test for inclusive range
138 start = parseInt(String(start), 10);
139 stop = parseInt(String(stop), 10);
140
141 if (start < 0) {
142 start = ranked.length + start;
143 }
144 if (stop < 0) {
145 stop = ranked.length + stop;
146 }
147
148 // start must be less then stop
149 if (start > stop) {
150 return mockInstance._callCallback(callback, null, []);
151 }
152 // console.log(ranked, start, stop + 1);
153
154 // make slice inclusive
155 ranked = ranked.slice(start, stop + 1);
156
157 var range = [],
158 mintest,
159 maxtest;
160 for (var i=0, parts, s, score, m; i < ranked.length; i++) {
161 parts = ranked[i].split(rankedDelimiter);
162 s = parts[0];
163 score = parseFloat(s);
164 m = parts.slice(1).join(rankedDelimiter);
165 range.push(m);
166 if (withscores && withscores.toLowerCase() === 'withscores') {
167 range.push(s);
168 }
169 }
170 mockInstance._callCallback(callback, null, range);
171}
172
173/**
174getRangeByScore (zrangebyscore & zrevrangebyscore)
175**/
176var getRangeByScore = function(
177 mockInstance,
178 key,
179 min,
180 max,
181 withscores,
182 limit,
183 offset,
184 count,
185 callback,
186 reversed) {
187
188 var len = arguments.length;
189 if (len < 4) {
190 return
191 }
192 if ('function' === typeof withscores) {
193 callback = withscores;
194 withscores = undefined;
195 }
196 if ('function' === typeof limit) {
197 callback = limit;
198 limit = undefined;
199 }
200 if (callback == undefined) {
201 callback = mockCallback;
202 }
203 if (!validKeyType(mockInstance, key, callback)) {
204 return
205 }
206
207 initKey(mockInstance, key);
208
209 var ranked = getRankedList(mockInstance, key);
210 if (reversed) {
211 ranked.reverse();
212 }
213
214 // check for infinity flags
215 if (min.toString() === '-inf') {
216 min = MIN_SCORE_VALUE;
217 }
218 if (max.toString() === '+inf') {
219 max = MAX_SCORE_VALUE;
220 }
221 // handles the reversed case
222 if (min.toString() === '+inf') {
223 min = MAX_SCORE_VALUE;
224 }
225 if (max.toString() === '-inf') {
226 max = MIN_SCORE_VALUE;
227 }
228
229 // convert to string so we can test for inclusive range
230 min = String(min);
231 max = String(max);
232
233 // ranges inclusive?
234 var minlt = false;
235 var maxlt = false;
236 if (min[0] === '(') {
237 min = min.substring(1);
238 minlt = true;
239 }
240 if (max[0] === '(') {
241 max = max.substring(1);
242 maxlt = true;
243 }
244 // convert to float
245 min = parseFloat(min);
246 max = parseFloat(max);
247
248 // console.log('checkpoint', ranked, min, max, withscores, callback, minlt, maxlt);
249 var range = [],
250 mintest,
251 maxtest;
252 for (var i=0, parts, s, score, m; i < ranked.length; i++) {
253 parts = ranked[i].split(rankedDelimiter);
254 s = parts[0];
255 score = parseFloat(s);
256 mintest = (minlt) ? (min < score) : (min <= score);
257 maxtest = (maxlt) ? (score < max) : (score <= max);
258
259 // console.log('test', s, score, mintest, maxtest);
260 if (!mintest || !maxtest) {
261 continue;
262 }
263 m = parts.slice(1).join(rankedDelimiter);
264 range.push(m);
265 if (withscores && withscores.toLowerCase() === 'withscores') {
266 // score as string
267 range.push(s);
268 }
269 }
270 // console.log('range', range);
271 // do we need to slice the out put?
272 if (limit && limit.toLowerCase() === 'limit' && offset && count) {
273 offset = parseInt(offset, 10);
274 count = parseInt(count, 10);
275 // withscores needs to adjust the offset and count
276 if (withscores && withscores.toLowerCase() === 'withscores') {
277 offset *= 2;
278 count *= 2;
279 }
280 range = range.slice(offset, offset + count);
281 }
282
283 mockInstance._callCallback(callback, null, range);
284
285}
286
287// ZADD key [NX|XX] [CH] [INCR] score member [score member ...]
288// Add one or more members to a sorted set, or update its score if it already exists
289exports.zadd = function(mockInstance, key) {
290
291 var len = arguments.length;
292 if (len <= 3) {
293 return
294 }
295 var callback = helpers.parseCallback(arguments);
296 if (!validKeyType(mockInstance, key, callback)) {
297 return
298 }
299 // init key
300 initKey(mockInstance, key);
301
302 // declare opts
303 var nx = false,
304 xx = false,
305 ch = false,
306 incr = false;
307 var start = 2,
308 count = 0;
309
310 for (var i=start; i < len; i++) {
311 var opt = arguments[i];
312 opt = opt.toString().toLowerCase();
313 // Don't update already existing elements. Always add new elements.
314 if (opt === 'nx') {
315 nx = true;
316 continue;
317 }
318 // Only update elements that already exist. Never add elements.
319 if (opt === 'xx') {
320 xx = true;
321 continue;
322 }
323 // Total number of elements changed
324 if (opt === 'ch') {
325 ch = true;
326 continue;
327 }
328 if (opt === 'incr') {
329 incr = true;
330 continue;
331 }
332 start = i;
333 break;
334
335 }
336
337 for (i = start; i < len; i += 2) {
338 // hold the score and make sure it isn't an opt
339 var score = arguments[i];
340
341 // did we reach the end?
342 if (len <= (i + 1)) {
343 break;
344 }
345 var member = Item._stringify(arguments[i + 1]);
346 var existingScore = mockInstance.storage[key].value[member];
347 var exists = existingScore != undefined;
348
349 // process opts
350 if ((nx && exists) || (xx && !exists)) {
351 continue;
352 }
353 // convert score to string
354 score = score.toString();
355
356 // updating score if memeber doesn't exist
357 // or if ch = true and score changes
358 if (!exists || (ch && existingScore != score)) {
359 count += 1;
360 }
361
362 // do we need to incr (existing score + score)?
363 if (incr && existingScore) {
364 score = parseFloat(existingScore) + parseFloat(score);
365 score = String(score);
366 }
367
368 // update score
369 mockInstance.storage[key].value[member] = score;
370
371 // only one member is allowed update
372 // if we have an incr
373 // this shold behave the same as zincrby
374 // so return the score instead of the updatedCount;
375 if (incr) {
376 count = score;
377 break;
378 }
379 }
380
381 if (callback) {
382 mockInstance._callCallback(callback, null, count);
383 }
384}
385
386// ZCARD key
387// Get the number of members in a sorted set
388exports.zcard = function(mockInstance, key, callback) {
389 var len = arguments.length;
390 if (len < 1) {
391 return
392 }
393 if (callback == undefined) {
394 callback = mockCallback;
395 }
396 if (!validKeyType(mockInstance, key, callback)) {
397 return
398 }
399 initKey(mockInstance, key);
400 var count = Object.keys(mockInstance.storage[key].value).length;
401 mockInstance._callCallback(callback, null, count);
402}
403
404// ZCOUNT key min max
405// Count the members in a sorted set with scores within the given values
406exports.zcount = function(mockInstance, key, min, max, callback) {
407 var parse = function(err, result) {
408 if (err) {
409 return mockInstance._callCallback(callback, err);
410 }
411 mockInstance._callCallback(callback, null, result.length);
412 }
413 exports.zrangebyscore(mockInstance, key, min, max, parse);
414}
415
416// ZINCRBY key increment member
417// Increment the score of a member in a sorted set
418exports.zincrby = function(mockInstance, key, increment, member, callback) {
419 var len = arguments.length;
420 if (len < 4) {
421 return
422 }
423 if (callback == undefined) {
424 callback = mockCallback;
425 }
426 if (!validKeyType(mockInstance, key, callback)) {
427 return
428 }
429 initKey(mockInstance, key);
430 member = Item._stringify(member);
431 var s = mockInstance.storage[key].value[member];
432 var score = parseFloat( s !== undefined ? s : '0');
433 increment = parseFloat(String(increment));
434 score += increment;
435 score = String(score);
436 mockInstance.storage[key].value[member] = score;
437 mockInstance._callCallback(callback, null, score);
438}
439
440// ZRANGE key start stop [WITHSCORES]
441// Return a range of members in a sorted set, by index
442exports.zrange = function(mockInstance, key, start, stop, withscores, callback) {
443 getRange(mockInstance, key, start, stop, withscores, callback, false);
444}
445
446// ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
447// Return a range of members in a sorted set, by score
448exports.zrangebyscore = function(
449 mockInstance,
450 key,
451 min,
452 max,
453 withscores,
454 limit,
455 offset,
456 count,
457 callback) {
458
459 getRangeByScore(
460 mockInstance,
461 key,
462 min,
463 max,
464 withscores,
465 limit,
466 offset,
467 count,
468 callback,
469 false);
470
471};
472
473// ZRANK key member
474// Determine the index of a member in a sorted set
475exports.zrank = function(mockInstance, key, member, callback) {
476 getRank(mockInstance, key, member, callback, false);
477}
478
479// ZREM key member [member ...]
480// Remove one or more members from a sorted set
481exports.zrem = function(mockInstance, key) {
482 var len = arguments.length;
483 if (len <= 3) {
484 return
485 }
486
487 var callback = helpers.parseCallback(arguments);
488 if (callback == undefined) {
489 callback = mockCallback;
490 }
491 if (!validKeyType(mockInstance, key, callback)) {
492 return
493 }
494 initKey(mockInstance, key);
495 // The number of members removed from the sorted set,
496 // not including non existing members.
497 var count = 0;
498 for (var i=2, member; i < len; i++) {
499 member = arguments[i];
500 if ('function' == typeof member) {
501 break;
502 }
503 member = Item._stringify(member);
504 if (mockInstance.storage[key].value[member]) {
505 delete mockInstance.storage[key].value[member];
506 count += 1;
507 }
508 }
509 mockInstance._callCallback(callback, null, count);
510}
511
512// ZREMRANGEBYRANK key start stop
513// Remove all members in a sorted set within the given indexes
514exports.zremrangebyrank = function(mockInstance, key, start, stop, callback) {
515
516 var deleteResults = function(err, results) {
517 if (err) {
518 return mockInstance._callCallback(callback, err);
519 }
520 var count = 0;
521 for (var i=0, member; i < results.length; i++) {
522 member = results[i];
523 if (mockInstance.storage[key].value[member]) {
524 delete mockInstance.storage[key].value[member];
525 count += 1;
526 }
527 }
528 mockInstance._callCallback(callback, null, count);
529 }
530 getRange(mockInstance, key, start, stop, deleteResults, false);
531}
532
533// ZREMRANGEBYSCORE key min max
534// Remove all members in a sorted set within the given scores
535exports.zremrangebyscore = function(mockInstance, key, min, max, callback) {
536
537 var deleteResults = function(err, results) {
538 if (err) {
539 return mockInstance._callCallback(callback, err);
540 }
541 var count = 0;
542 for (var i=0, member; i < results.length; i++) {
543 member = results[i];
544 if (mockInstance.storage[key].value[member]) {
545 delete mockInstance.storage[key].value[member];
546 count += 1;
547 }
548 }
549 mockInstance._callCallback(callback, null, count);
550 }
551 getRangeByScore(mockInstance, key, min, max, deleteResults, false);
552}
553
554
555// ZREVRANGE key start stop [WITHSCORES]
556// Return a range of members in a sorted set, by index, with scores ordered from high to low
557exports.zrevrange = function(mockInstance, key, start, stop, withscores, callback) {
558 getRange(mockInstance, key, start, stop, withscores, callback, true);
559}
560
561// ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
562// Return a range of members in a sorted set, by score, with scores ordered from high to low
563exports.zrevrangebyscore = function(
564 mockInstance,
565 key,
566 max,
567 min,
568 withscores,
569 limit,
570 offset,
571 count,
572 callback) {
573
574 getRangeByScore(
575 mockInstance,
576 key,
577 min,
578 max,
579 withscores,
580 limit,
581 offset,
582 count,
583 callback,
584 true);
585};
586
587// ZREVRANK key member
588// Determine the index of a member in a sorted set, with scores ordered from high to low
589exports.zrevrank = function(mockInstance, key, member, callback) {
590 getRank(mockInstance, key, member, callback, true);
591}
592
593// ZSCORE key member
594// Get the score associated with the given member in a sorted set
595exports.zscore = function(mockInstance, key, member, callback) {
596 var len = arguments.length;
597 if (len < 3) {
598 return
599 }
600 if (callback == undefined) {
601 callback = mockCallback;
602 }
603 if (!validKeyType(mockInstance, key, callback)) {
604 return
605 }
606 initKey(mockInstance, key);
607 var score = mockInstance.storage[key].value[Item._stringify(member)];
608 mockInstance._callCallback(callback, null, (score === undefined ? null : score));
609}
610
611// ZUNIONSTORE key argcount member, members...
612exports.zunionstore = function(mockInstance, destination, numKeys) {
613 if (arguments.length < 3) {
614 return
615 }
616
617 // Callback function (last arg)
618 var c = arguments[arguments.length - 1]
619 var callback = typeof c === 'function' && c || mockCallback
620
621 // Parse arguments
622 var argsArr = Array.prototype.slice.call(arguments).slice(1)
623 var srcKeys = argsArr.slice(2, 2 + Number(numKeys))
624
625 // Print out warning if bad keys were passed in
626 if (srcKeys.length === 0) {
627 console.warn('Warning: No keys passed in to ZUNIONSTORE') // eslint-disable-line no-console
628 }
629 if(srcKeys.some( function(key) {
630 return !key
631 })) {
632 console.warn('Warning: Undefined or null key(s) provided to ZUNIONSTORE:', srcKeys) // eslint-disable-line no-console
633 }
634
635 var sourcesProcessed = 0
636 srcKeys.forEach( function(srcKey) {
637 getRange( mockInstance, srcKey, 0, -1, 'withscores', function(err, srcVals) {
638 var srcItemsProcessed = 0
639
640 // Did we select an empty source?
641 if (!srcVals || srcVals.length === 0) {
642 sourcesProcessed++
643 // Done with all sources?
644 if (sourcesProcessed === srcKeys.length) {
645 initKey(mockInstance, destination);
646 mockInstance._callCallback(callback, null, Object.keys(mockInstance.storage[destination].value).length)
647 return;
648 }
649 }
650
651 // Add items one-by-one (because value / score order is flipped on zadd vs. zrange)
652 for(var i = 0; i < (srcVals.length -1); i = i+2) {
653 // score member
654 module.exports.zadd( mockInstance, destination, srcVals[i+1], srcVals[i])
655 srcItemsProcessed++
656
657 // Done with all items in this source?
658 if (srcItemsProcessed === srcVals.length / 2) {
659 sourcesProcessed++
660 }
661 // Done with all sources?
662 if (sourcesProcessed === srcKeys.length) {
663 initKey(mockInstance, destination);
664 mockInstance._callCallback(callback, null, Object.keys(mockInstance.storage[destination].value).length)
665 }
666 }
667 })
668 })
669
670 // TODO: Support: [WEIGHTS weight [weight ...]]
671 // TODO: Support: [AGGREGATE SUM|MIN|MAX]
672}
673
674
675/* Is the provided prop present in all provided objects? */
676var allObjsHaveKey = function(prop, objs) {
677 return objs.every( function(o) {
678 return !!o[prop]
679 })
680}
681
682/* Sum of the given prop, as found in all the given objects */
683var sumPropInObjs = function(prop, objs) {
684 return objs.reduce( function(sum, o) {
685 return sum + Number(o[prop] || '0')
686 }, 0)
687}
688
689// ZINTERSTORE key argcount member, members...
690exports.zinterstore = function(mockInstance, destination, numKeys) {
691 if (arguments.length < 3) {
692 return
693 }
694
695 // Callback function (last arg)
696 var c = arguments[arguments.length - 1]
697 var callback = typeof c === 'function' && c || mockCallback
698
699 // Parse arguments
700 var argsArr = Array.prototype.slice.call(arguments).slice(1)
701 var srcKeys = argsArr.slice(2, 2 + Number(numKeys))
702
703 // Print out warning if bad keys were passed in
704 if (srcKeys.length === 0) {
705 console.warn('Warning: No keys passed in to ZUNIONSTORE') // eslint-disable-line no-console
706 }
707 if (srcKeys.some( function(key) {
708 return !key
709 })) {
710 console.warn('Warning: Undefined or null key(s) provided to ZUNIONSTORE:', srcKeys) // eslint-disable-line no-console
711 }
712
713 // Destination storage
714 var dest = {} // Key -> Score mapping
715
716 // Source keys storage (filtering out non-existent ones)
717 var sources = srcKeys.map( function(srcKey) {
718 return mockInstance.storage[srcKey] ? mockInstance.storage[srcKey].value : null
719 }).filter( function(src) {
720 return !!src
721 })
722
723 // Compute intersection (inefficiently)
724 sources.forEach( function(source) {
725 Object.keys(source).forEach(function(key) {
726 if (allObjsHaveKey(key, sources)) {
727 dest[key] = String(sumPropInObjs(key, sources))
728 }
729 })
730 })
731
732 // Store results
733 initKey(mockInstance, destination);
734 var destValues = Object.keys(dest)
735 destValues.forEach(function(value) {
736 module.exports.zadd( mockInstance, destination, dest[value], value)
737 })
738
739 mockInstance._callCallback(callback, null, destValues.length)
740
741 // TODO: Support: [WEIGHTS weight [weight ...]]
742 // TODO: Support: [AGGREGATE SUM|MIN|MAX]
743}