1 |
|
2 |
|
3 | var helpers = require("./helpers.js");
|
4 | var Item = require("./item.js");
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 | var MAX_SCORE_VALUE = 9007199254740992;
|
34 | var MIN_SCORE_VALUE = -MAX_SCORE_VALUE;
|
35 |
|
36 | var mockCallback = helpers.mockCallback;
|
37 |
|
38 | var validKeyType = function(mockInstance, key, callback) {
|
39 | return helpers.validKeyType(mockInstance, key, 'zset', callback)
|
40 | }
|
41 |
|
42 | var initKey = function(mockInstance, key) {
|
43 | return helpers.initKey(mockInstance, key, Item.createSortedSet);
|
44 | }
|
45 |
|
46 |
|
47 | var rankedDelimiter = '#';
|
48 |
|
49 |
|
50 |
|
51 |
|
52 | var getRankedList = function(mockInstance, key) {
|
53 |
|
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 |
|
61 | items.sort(
|
62 | function (a, b) {
|
63 | return a[0] - b[0] || a[1].localeCompare(b[1]);
|
64 | });
|
65 |
|
66 |
|
67 | return items.map(function (value, index) {
|
68 | return value[1];
|
69 | });
|
70 | }
|
71 |
|
72 |
|
73 |
|
74 |
|
75 | var 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 |
|
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 |
|
111 |
|
112 |
|
113 | var 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 |
|
133 | if (reversed) {
|
134 | ranked.reverse();
|
135 | }
|
136 |
|
137 |
|
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 |
|
149 | if (start > stop) {
|
150 | return mockInstance._callCallback(callback, null, []);
|
151 | }
|
152 |
|
153 |
|
154 |
|
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 |
|
174 |
|
175 |
|
176 | var 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 |
|
215 | if (min.toString() === '-inf') {
|
216 | min = MIN_SCORE_VALUE;
|
217 | }
|
218 | if (max.toString() === '+inf') {
|
219 | max = MAX_SCORE_VALUE;
|
220 | }
|
221 |
|
222 | if (min.toString() === '+inf') {
|
223 | min = MAX_SCORE_VALUE;
|
224 | }
|
225 | if (max.toString() === '-inf') {
|
226 | max = MIN_SCORE_VALUE;
|
227 | }
|
228 |
|
229 |
|
230 | min = String(min);
|
231 | max = String(max);
|
232 |
|
233 |
|
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 |
|
245 | min = parseFloat(min);
|
246 | max = parseFloat(max);
|
247 |
|
248 |
|
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 |
|
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 |
|
267 | range.push(s);
|
268 | }
|
269 | }
|
270 |
|
271 |
|
272 | if (limit && limit.toLowerCase() === 'limit' && offset && count) {
|
273 | offset = parseInt(offset, 10);
|
274 | count = parseInt(count, 10);
|
275 |
|
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 |
|
288 |
|
289 | exports.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 |
|
300 | initKey(mockInstance, key);
|
301 |
|
302 |
|
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 |
|
314 | if (opt === 'nx') {
|
315 | nx = true;
|
316 | continue;
|
317 | }
|
318 |
|
319 | if (opt === 'xx') {
|
320 | xx = true;
|
321 | continue;
|
322 | }
|
323 |
|
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 |
|
339 | var score = arguments[i];
|
340 |
|
341 |
|
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 |
|
350 | if ((nx && exists) || (xx && !exists)) {
|
351 | continue;
|
352 | }
|
353 |
|
354 | score = score.toString();
|
355 |
|
356 |
|
357 |
|
358 | if (!exists || (ch && existingScore != score)) {
|
359 | count += 1;
|
360 | }
|
361 |
|
362 |
|
363 | if (incr && existingScore) {
|
364 | score = parseFloat(existingScore) + parseFloat(score);
|
365 | score = String(score);
|
366 | }
|
367 |
|
368 |
|
369 | mockInstance.storage[key].value[member] = score;
|
370 |
|
371 |
|
372 |
|
373 |
|
374 |
|
375 | if (incr) {
|
376 | count = score;
|
377 | break;
|
378 | }
|
379 | }
|
380 |
|
381 | if (callback) {
|
382 | mockInstance._callCallback(callback, null, count);
|
383 | }
|
384 | }
|
385 |
|
386 |
|
387 |
|
388 | exports.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 |
|
405 |
|
406 | exports.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 |
|
417 |
|
418 | exports.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 |
|
441 |
|
442 | exports.zrange = function(mockInstance, key, start, stop, withscores, callback) {
|
443 | getRange(mockInstance, key, start, stop, withscores, callback, false);
|
444 | }
|
445 |
|
446 |
|
447 |
|
448 | exports.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 |
|
474 |
|
475 | exports.zrank = function(mockInstance, key, member, callback) {
|
476 | getRank(mockInstance, key, member, callback, false);
|
477 | }
|
478 |
|
479 |
|
480 |
|
481 | exports.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 |
|
496 |
|
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 |
|
513 |
|
514 | exports.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 |
|
534 |
|
535 | exports.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 |
|
556 |
|
557 | exports.zrevrange = function(mockInstance, key, start, stop, withscores, callback) {
|
558 | getRange(mockInstance, key, start, stop, withscores, callback, true);
|
559 | }
|
560 |
|
561 |
|
562 |
|
563 | exports.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 |
|
588 |
|
589 | exports.zrevrank = function(mockInstance, key, member, callback) {
|
590 | getRank(mockInstance, key, member, callback, true);
|
591 | }
|
592 |
|
593 |
|
594 |
|
595 | exports.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 |
|
612 | exports.zunionstore = function(mockInstance, destination, numKeys) {
|
613 | if (arguments.length < 3) {
|
614 | return
|
615 | }
|
616 |
|
617 |
|
618 | var c = arguments[arguments.length - 1]
|
619 | var callback = typeof c === 'function' && c || mockCallback
|
620 |
|
621 |
|
622 | var argsArr = Array.prototype.slice.call(arguments).slice(1)
|
623 | var srcKeys = argsArr.slice(2, 2 + Number(numKeys))
|
624 |
|
625 |
|
626 | if (srcKeys.length === 0) {
|
627 | console.warn('Warning: No keys passed in to ZUNIONSTORE')
|
628 | }
|
629 | if(srcKeys.some( function(key) {
|
630 | return !key
|
631 | })) {
|
632 | console.warn('Warning: Undefined or null key(s) provided to ZUNIONSTORE:', srcKeys)
|
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 |
|
641 | if (!srcVals || srcVals.length === 0) {
|
642 | sourcesProcessed++
|
643 |
|
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 |
|
652 | for(var i = 0; i < (srcVals.length -1); i = i+2) {
|
653 |
|
654 | module.exports.zadd( mockInstance, destination, srcVals[i+1], srcVals[i])
|
655 | srcItemsProcessed++
|
656 |
|
657 |
|
658 | if (srcItemsProcessed === srcVals.length / 2) {
|
659 | sourcesProcessed++
|
660 | }
|
661 |
|
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 |
|
671 |
|
672 | }
|
673 |
|
674 |
|
675 |
|
676 | var allObjsHaveKey = function(prop, objs) {
|
677 | return objs.every( function(o) {
|
678 | return !!o[prop]
|
679 | })
|
680 | }
|
681 |
|
682 |
|
683 | var sumPropInObjs = function(prop, objs) {
|
684 | return objs.reduce( function(sum, o) {
|
685 | return sum + Number(o[prop] || '0')
|
686 | }, 0)
|
687 | }
|
688 |
|
689 |
|
690 | exports.zinterstore = function(mockInstance, destination, numKeys) {
|
691 | if (arguments.length < 3) {
|
692 | return
|
693 | }
|
694 |
|
695 |
|
696 | var c = arguments[arguments.length - 1]
|
697 | var callback = typeof c === 'function' && c || mockCallback
|
698 |
|
699 |
|
700 | var argsArr = Array.prototype.slice.call(arguments).slice(1)
|
701 | var srcKeys = argsArr.slice(2, 2 + Number(numKeys))
|
702 |
|
703 |
|
704 | if (srcKeys.length === 0) {
|
705 | console.warn('Warning: No keys passed in to ZUNIONSTORE')
|
706 | }
|
707 | if (srcKeys.some( function(key) {
|
708 | return !key
|
709 | })) {
|
710 | console.warn('Warning: Undefined or null key(s) provided to ZUNIONSTORE:', srcKeys)
|
711 | }
|
712 |
|
713 |
|
714 | var dest = {}
|
715 |
|
716 |
|
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 |
|
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 |
|
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 |
|
742 |
|
743 | }
|