@depup/fakeredis 2.0.0-depup.0
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/.codeclimate.yml +15 -0
- package/.editorconfig +16 -0
- package/.jshintrc +6 -0
- package/.travis.yml +14 -0
- package/Changelog.md +53 -0
- package/README.md +31 -0
- package/changes.json +10 -0
- package/covertest +16 -0
- package/lib/backend.js +1951 -0
- package/lib/connection.js +474 -0
- package/lib/helpers.js +172 -0
- package/main.js +268 -0
- package/package.json +56 -0
- package/redis.test.js +2352 -0
- package/runtest +10 -0
- package/test.js +1025 -0
package/lib/backend.js
ADDED
|
@@ -0,0 +1,1951 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
// Error replies.
|
|
5
|
+
|
|
6
|
+
var ERROR = function (message) {
|
|
7
|
+
this.getError = function () { return message; };
|
|
8
|
+
this.toString = function () { return "<ERROR<" + message + ">>"; };
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
var BAD_TYPE = new ERROR('Operation against a key holding the wrong kind of value');
|
|
12
|
+
var BAD_KEY = new ERROR('no such key');
|
|
13
|
+
var BAD_INT = new ERROR('value is not an integer or out of range');
|
|
14
|
+
var BAD_FLOAT = new ERROR('value is not a valid float');
|
|
15
|
+
var BAD_ARGS = new ERROR('wrong number of arguments');
|
|
16
|
+
var BAD_SYNTAX = new ERROR('syntax error');
|
|
17
|
+
var BAD_INDEX = new ERROR('index out of range');
|
|
18
|
+
var BAD_SORT = new ERROR('One or more scores can\'t be converted into double');
|
|
19
|
+
|
|
20
|
+
var BAD_BIT1 = new ERROR('bit offset is not an integer or out of range');
|
|
21
|
+
var BAD_BIT2 = new ERROR('bit is not an integer or out of range');
|
|
22
|
+
var BAD_SETEX = new ERROR('invalid expire time in SETEX');
|
|
23
|
+
var BAD_ZUIS = new ERROR('at least 1 input key is needed for ZUNIONSTORE/ZINTERSTORE');
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
// Status replies.
|
|
27
|
+
|
|
28
|
+
var STATUS = function (message) {
|
|
29
|
+
this.getStatus = function () { return message; };
|
|
30
|
+
this.toString = function () { return "<STATUS<" + message + ">>"; };
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
var OK = new STATUS('OK');
|
|
34
|
+
var PONG = new STATUS('PONG');
|
|
35
|
+
var NONE = new STATUS('none');
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
// Redis types.
|
|
39
|
+
|
|
40
|
+
var VALID_TYPE = function () {};
|
|
41
|
+
var TYPE = function (type, makePrimitive) {
|
|
42
|
+
var Constr = function (value) {
|
|
43
|
+
if (!(this instanceof VALID_TYPE))
|
|
44
|
+
return new Constr(value);
|
|
45
|
+
if (!value)
|
|
46
|
+
value = makePrimitive();
|
|
47
|
+
|
|
48
|
+
this.value = value;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
Constr.getStatus = function () { return type; };
|
|
52
|
+
Constr.prototype = new VALID_TYPE;
|
|
53
|
+
Constr.prototype.toString = function () { return "<TYPE<" + type + ">>"; };
|
|
54
|
+
Constr.prototype.TYPE = Constr;
|
|
55
|
+
return Constr;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
var EMPTY_STR = { toString: function () { return ""; }, length: 0, copy: function () {} };
|
|
59
|
+
var STRING = TYPE("string", function () { return EMPTY_STR; });
|
|
60
|
+
var LIST = TYPE("list", function () { return []; });
|
|
61
|
+
var HASH = TYPE("hash", function () { return {}; });
|
|
62
|
+
var SET = TYPE("set", function () { return {}; });
|
|
63
|
+
var ZSET = TYPE("zset", function () { return {}; });
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
// Utils.
|
|
67
|
+
|
|
68
|
+
var arr = function (obj) {
|
|
69
|
+
var i, n = obj.length, out = [];
|
|
70
|
+
for (i = 0; i < n; i++)
|
|
71
|
+
out[i] = obj[i];
|
|
72
|
+
|
|
73
|
+
return out;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
var range = function (min, max) {
|
|
77
|
+
var xlo, xhi;
|
|
78
|
+
|
|
79
|
+
if ((xlo = min.substr(0, 1) === '('))
|
|
80
|
+
min = str2float(min.substr(1));
|
|
81
|
+
else
|
|
82
|
+
min = str2float(min);
|
|
83
|
+
|
|
84
|
+
if (min instanceof ERROR)
|
|
85
|
+
return min;
|
|
86
|
+
|
|
87
|
+
if ((xhi = max.substr(0, 1) === '('))
|
|
88
|
+
max = str2float(max.substr(1));
|
|
89
|
+
else
|
|
90
|
+
max = str2float(max);
|
|
91
|
+
|
|
92
|
+
if (max instanceof ERROR)
|
|
93
|
+
return max;
|
|
94
|
+
|
|
95
|
+
return function (num) {
|
|
96
|
+
return !((xlo && num <= min) || (num < min) || (xhi && num >= max) || (num > max));
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
var slice = function (arr, start, stop, asCount) {
|
|
101
|
+
start = str2int(start);
|
|
102
|
+
stop = str2int(stop);
|
|
103
|
+
if (start instanceof ERROR) return start;
|
|
104
|
+
if (stop instanceof ERROR) return stop;
|
|
105
|
+
|
|
106
|
+
if (arr.slice) {
|
|
107
|
+
var n = arr.length;
|
|
108
|
+
if (asCount) {
|
|
109
|
+
if (start < 0) {
|
|
110
|
+
start = 0; // Redis is inconsistent about this, ZRANGEBYSCORE will return an empty multibulk on negative offset
|
|
111
|
+
stop = 0; // whilst SORT will return as if the offset was 0. Best to lint these away with client-side errors.
|
|
112
|
+
}
|
|
113
|
+
else if (stop < 0) stop = n;
|
|
114
|
+
else stop += start;
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
if (start < 0) start = n + start;
|
|
118
|
+
if (stop < 0) stop = n + stop;
|
|
119
|
+
stop++;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (start >= stop)
|
|
123
|
+
return [];
|
|
124
|
+
else
|
|
125
|
+
return arr.slice(start < 0 ? 0 : start, stop > n ? n : stop);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
else
|
|
129
|
+
return arr;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
var str2float = function (string) {
|
|
133
|
+
var value = Number(string);
|
|
134
|
+
if (typeof string !== 'string') throw new Error("WOOT! str2float: '" + string + "' not a string.");
|
|
135
|
+
if (string === '+inf') value = Number.POSITIVE_INFINITY;
|
|
136
|
+
else if (string === '-inf') value = Number.NEGATIVE_INFINITY;
|
|
137
|
+
else if (!string || (!value && value !== 0)) return BAD_FLOAT;
|
|
138
|
+
return value;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
var str2int = function (string) {
|
|
142
|
+
var value = str2float(string);
|
|
143
|
+
if (value instanceof ERROR || value % 1 !== 0) return BAD_INT;
|
|
144
|
+
return value;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
var pattern = function (string) {
|
|
148
|
+
string = string.replace(/([+{($^|.\\])/g, '\\' + '$1');
|
|
149
|
+
string = string.replace(/(^|[^\\])([*?])/g, '$1.$2');
|
|
150
|
+
string = '^' + string + '$';
|
|
151
|
+
|
|
152
|
+
var pattern = new RegExp(string);
|
|
153
|
+
return pattern.test.bind(pattern);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
var populationCount = function (data) {
|
|
157
|
+
var count;
|
|
158
|
+
for (count = 0; data > 0; count++) {
|
|
159
|
+
data &= data - 1;
|
|
160
|
+
}
|
|
161
|
+
return count;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// Keyspace and pubsub.
|
|
165
|
+
|
|
166
|
+
exports.Backend = function () {
|
|
167
|
+
var state
|
|
168
|
+
, dbs = {}
|
|
169
|
+
, delrev = {}
|
|
170
|
+
, rev = 0
|
|
171
|
+
|
|
172
|
+
, subs = []
|
|
173
|
+
, call = []
|
|
174
|
+
, tick = false
|
|
175
|
+
, nextTick = function () {
|
|
176
|
+
var c, func;
|
|
177
|
+
tick = false;
|
|
178
|
+
c = call.splice(0, call.length);
|
|
179
|
+
while ((func = c.shift())) func();
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
// Select.
|
|
184
|
+
// Selected keyspace is NOT relevant to pubsub.
|
|
185
|
+
this.selectDB = function (id) {
|
|
186
|
+
if (typeof id !== "number" || id % 1 !== 0)
|
|
187
|
+
throw new Error("Invalid database id: " + id);
|
|
188
|
+
|
|
189
|
+
// Select or instantiate.
|
|
190
|
+
var db = dbs[id] || (dbs[id] = {});
|
|
191
|
+
state = db;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// Connections start in database 0.
|
|
195
|
+
this.selectDB(0);
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
// Typed getKey.
|
|
199
|
+
|
|
200
|
+
this.getKey = function (Type, key, make) {
|
|
201
|
+
var entry = state[key];
|
|
202
|
+
|
|
203
|
+
if (Type && !Type.getStatus)
|
|
204
|
+
throw new Error("WOOT! Type param for getKey is not a valid Type.");
|
|
205
|
+
if (key === undefined)
|
|
206
|
+
throw new Error("WOOT! key param for getKey is undefined.");
|
|
207
|
+
|
|
208
|
+
if (entry) {
|
|
209
|
+
if (entry.expire < Date.now()) {
|
|
210
|
+
delete state[key];
|
|
211
|
+
delrev[key] = ++rev;
|
|
212
|
+
entry = null;
|
|
213
|
+
}
|
|
214
|
+
else if (!(entry.value instanceof VALID_TYPE))
|
|
215
|
+
throw new Error("WOOT! keyspace entry value is not a valid Type.");
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (Type) {
|
|
219
|
+
if (entry && !(entry.value instanceof Type))
|
|
220
|
+
return BAD_TYPE;
|
|
221
|
+
if (!entry && make)
|
|
222
|
+
return new Type;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return (entry && entry.value) || null;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
this.setKey = function (key, value) {
|
|
229
|
+
if (value) {
|
|
230
|
+
if (!(value instanceof VALID_TYPE))
|
|
231
|
+
throw new Error("WOOT! Value doesn't have a valid type.");
|
|
232
|
+
|
|
233
|
+
rev++;
|
|
234
|
+
state[key] = { value: value };
|
|
235
|
+
state[key].rev = rev;
|
|
236
|
+
delete delrev[key];
|
|
237
|
+
|
|
238
|
+
this.pub(this.UPDATE, key);
|
|
239
|
+
|
|
240
|
+
return 1;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
else if (state[key]) {
|
|
244
|
+
rev++;
|
|
245
|
+
delrev[key] = rev;
|
|
246
|
+
delete state[key];
|
|
247
|
+
|
|
248
|
+
return 1;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return 0;
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
this.upsetKey = function (key, value) {
|
|
255
|
+
if (!value)
|
|
256
|
+
throw new Error("WOOT! Update key with a falsy value.");
|
|
257
|
+
if (!(value instanceof VALID_TYPE))
|
|
258
|
+
throw new Error("WOOT! Value doesn't have a valid type.");
|
|
259
|
+
|
|
260
|
+
if (state[key] && state[key].expire >= Date.now()) {
|
|
261
|
+
if (state[key].value !== value)
|
|
262
|
+
throw new Error("WOOT! Chaning value containers during upsetKey.");
|
|
263
|
+
|
|
264
|
+
rev++;
|
|
265
|
+
state[key].value = value;
|
|
266
|
+
state[key].rev = rev;
|
|
267
|
+
|
|
268
|
+
this.pub(this.UPDATE, key);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
else
|
|
272
|
+
this.setKey(key, value);
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
this.getExpire = function (key) {
|
|
276
|
+
var entry = state[key];
|
|
277
|
+
|
|
278
|
+
if (!entry || entry.expire < Date.now()) {
|
|
279
|
+
delete state[key];
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return entry.expire;
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
this.setExpire = function (key, expire) {
|
|
287
|
+
var entry = state[key];
|
|
288
|
+
|
|
289
|
+
if (!entry || entry.expire < Date.now()) {
|
|
290
|
+
delete state[key];
|
|
291
|
+
return 0;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
else if (expire) {
|
|
295
|
+
entry.expire = expire;
|
|
296
|
+
return 1;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
else if (entry.expire) {
|
|
300
|
+
delete entry.expire;
|
|
301
|
+
return 1;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return 0;
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
this.getKeys = function () {
|
|
308
|
+
var keys = []
|
|
309
|
+
, key;
|
|
310
|
+
|
|
311
|
+
for (key in state)
|
|
312
|
+
if (this.getKey(null, key))
|
|
313
|
+
keys.push(key);
|
|
314
|
+
|
|
315
|
+
return keys;
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
this.renameKey = function (keyA, keyB) {
|
|
319
|
+
if (!this.getKey(null, keyA))
|
|
320
|
+
return false;
|
|
321
|
+
|
|
322
|
+
rev++;
|
|
323
|
+
state[keyB] = state[keyA];
|
|
324
|
+
state[keyB].rev = rev++;
|
|
325
|
+
delete state[keyA];
|
|
326
|
+
|
|
327
|
+
this.pub(this.UPDATE, keyB);
|
|
328
|
+
|
|
329
|
+
return true;
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
// Keyspace change event.
|
|
334
|
+
|
|
335
|
+
this.UPDATE = new STATUS("Key value updated.");
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
// For implementing watch and stuff.
|
|
339
|
+
|
|
340
|
+
this.getRevision = function (key) {
|
|
341
|
+
this.getKey(null, key);
|
|
342
|
+
return (state[key] && state[key].rev) || delrev[key] || 0;
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
// Publish / subscribe backend.
|
|
347
|
+
|
|
348
|
+
this.pub = function (channel, message) {
|
|
349
|
+
if (!channel && channel !== '') throw new Error("WOOT! Publishing to a falsy, non-string channel : [" + channel + '] ' + message);
|
|
350
|
+
if (!message && message !== '') throw new Error("WOOT! Publishing a falsy, non-string message : [" + channel + '] ' + message);
|
|
351
|
+
|
|
352
|
+
var i, n = subs.length, sub, x = 0;
|
|
353
|
+
for (i = 0; i < n; i++) {
|
|
354
|
+
sub = subs[i];
|
|
355
|
+
|
|
356
|
+
if (sub.channel === channel || (sub.pattern !== null && sub.channel(channel))) {
|
|
357
|
+
if (sub.pattern !== null)
|
|
358
|
+
call.push(sub.client.pushMessage.bind(sub.client, 'pmessage', sub.pattern, channel, message));
|
|
359
|
+
else
|
|
360
|
+
call.push(sub.client.pushMessage.bind(sub.client, 'message', channel, message));
|
|
361
|
+
|
|
362
|
+
x++;
|
|
363
|
+
if (!tick) {
|
|
364
|
+
tick = true;
|
|
365
|
+
process.nextTick(nextTick);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return x;
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
// p - true/false
|
|
374
|
+
// channel - string
|
|
375
|
+
// client { push ( pattern, channel, message ) }
|
|
376
|
+
|
|
377
|
+
this.sub = function (p, channel, client) {
|
|
378
|
+
if (!channel && channel !== '') throw new Error("WOOT! Subscribing to a falsy, non-string channel : [" + channel + ']');
|
|
379
|
+
if (!client || !client.pushMessage) throw new Error("WOOT! Subscribing an invalid client : " + client);
|
|
380
|
+
if (typeof channel === 'function') throw new Error("WOOT! Subscribing to a function : " + channel);
|
|
381
|
+
|
|
382
|
+
var i, n = subs.length, sub, found = false;
|
|
383
|
+
for (i = 0; i < n; i++) {
|
|
384
|
+
sub = subs[i];
|
|
385
|
+
if (sub.client === client && ((p && sub.pattern === channel) || (!p && sub.channel === channel))) {
|
|
386
|
+
found = true;
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
var x = this.numSubs(client);
|
|
392
|
+
|
|
393
|
+
if (!found) {
|
|
394
|
+
x++;
|
|
395
|
+
|
|
396
|
+
subs.push({ pattern: p ? channel : null, channel: p ? pattern(channel) : channel, client: client });
|
|
397
|
+
process.nextTick(client.pushMessage.bind(client, p ? 'psubscribe' : 'subscribe', channel, x));
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return x;
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
this.unsub = function (p, channel, client) {
|
|
404
|
+
if (!channel && channel !== '' && channel !== null) throw new Error("WOOT! Unsubscribing from a falsy, non-string, non-null channel : [" + channel + ']');
|
|
405
|
+
if (!client || !client.pushMessage) throw new Error("WOOT! Unsubscribing an invalid client : " + client);
|
|
406
|
+
|
|
407
|
+
var x = this.numSubs(client);
|
|
408
|
+
|
|
409
|
+
var i, n = subs.length, sub;
|
|
410
|
+
for (i = 0; i < n; i++) {
|
|
411
|
+
sub = subs[i];
|
|
412
|
+
if (sub.client !== client)
|
|
413
|
+
continue;
|
|
414
|
+
|
|
415
|
+
if ((p && sub.pattern !== null && (channel === null || sub.pattern === channel)) || (!p && sub.pattern === null && (channel === null || sub.channel === channel))) {
|
|
416
|
+
x--;
|
|
417
|
+
subs.splice(i, 1);
|
|
418
|
+
process.nextTick(client.pushMessage.bind(client, p ? 'punsubscribe' : 'unsubscribe', p ? sub.pattern : sub.channel, x));
|
|
419
|
+
i--; n--;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return x;
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
this.numSubs = function (client) {
|
|
427
|
+
var i, n = subs.length, x = 0;
|
|
428
|
+
for (i = 0; i < n; i++)
|
|
429
|
+
if (subs[i].client === client)
|
|
430
|
+
x++;
|
|
431
|
+
|
|
432
|
+
return x;
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
// Redis commands.
|
|
439
|
+
|
|
440
|
+
exports.Backend.prototype = {
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
// Keys.
|
|
444
|
+
|
|
445
|
+
DEL: function () {
|
|
446
|
+
var i, n = arguments.length, x = 0;
|
|
447
|
+
if (!n) return BAD_ARGS;
|
|
448
|
+
for (i = 0; i < n; i++)
|
|
449
|
+
if (this.setKey(arguments[i], null)) x++;
|
|
450
|
+
|
|
451
|
+
return x;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
, EXISTS: function (key) {
|
|
455
|
+
return this.getKey(null, key)? 1 : 0;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
, PEXPIREAT: function (key, time) {
|
|
459
|
+
time = str2int(time);
|
|
460
|
+
if (time instanceof ERROR) return time;
|
|
461
|
+
return this.setExpire(key, time);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
, EXPIREAT: function (key, time) {
|
|
465
|
+
time = str2int(time);
|
|
466
|
+
if (time instanceof ERROR) return time;
|
|
467
|
+
return this.setExpire(key, time * 1000);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
, PEXPIRE: function (key, time) {
|
|
471
|
+
time = str2int(time);
|
|
472
|
+
if (time instanceof ERROR) return time;
|
|
473
|
+
return this.setExpire(key, time + Date.now());
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
, EXPIRE: function (key, time) {
|
|
477
|
+
time = str2int(time);
|
|
478
|
+
if (time instanceof ERROR) return time;
|
|
479
|
+
return this.setExpire(key, time * 1000 + Date.now());
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
, PERSIST: function (key) {
|
|
483
|
+
return this.PEXPIREAT(key, "0");
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
, PTTL: function (key) {
|
|
487
|
+
var ttl = this.getExpire(key);
|
|
488
|
+
if (ttl) return ttl - Date.now();
|
|
489
|
+
else return - 1;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
, RANDOMKEY: function () {
|
|
493
|
+
var keys = this.getKeys(), n = keys && keys.length;
|
|
494
|
+
if (n) return keys[Math.floor(Math.random()* n)];
|
|
495
|
+
else return null;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
, RENAME: function (key, newkey) {
|
|
499
|
+
return this.renameKey(key, newkey)? OK : BAD_KEY;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
, RENAMENX: function (key, newkey) {
|
|
503
|
+
if (!this.EXISTS(key)) return BAD_KEY;
|
|
504
|
+
if (this.EXISTS(newkey)) return 0;
|
|
505
|
+
if (!this.renameKey(key, newkey)) throw new Error("WOOT! Couldn't rename.");
|
|
506
|
+
return 1;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
, TTL: function (key) {
|
|
510
|
+
var ttl = this.getExpire(key);
|
|
511
|
+
if (ttl) return Math.ceil((ttl - Date.now())/ 1000);
|
|
512
|
+
else return - 1;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
, TYPE: function (key) {
|
|
516
|
+
var K = this.getKey(null, key);
|
|
517
|
+
return K ? K.TYPE : NONE;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
, KEYS: function (pat) {
|
|
521
|
+
var keys = this.getKeys().filter(pattern(pat));
|
|
522
|
+
keys.sort();
|
|
523
|
+
return keys;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
, SCAN: function () {
|
|
527
|
+
return this._scan(this.getKeys(), 1, arguments[0], arguments[1], arguments[2], arguments[3], arguments[4]);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
, SSCAN: function () {
|
|
531
|
+
return this._scan(this.SMEMBERS(arguments[0]), 1, arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
, HSCAN: function () {
|
|
535
|
+
return this._scan(this.HGETALL(arguments[0]), 2, arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
, ZSCAN: function () {
|
|
539
|
+
return this._scan(this.ZRANGE(arguments[0], '0', '-1', 'WITHSCORES'), 2, arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
, _scan: function (allKeys, size, cursor, opt1, opt1val, opt2, opt2val) {
|
|
543
|
+
cursor = str2int(cursor);
|
|
544
|
+
if (cursor instanceof ERROR)
|
|
545
|
+
return cursor;
|
|
546
|
+
|
|
547
|
+
var count = 10;
|
|
548
|
+
var matchPattern = null;
|
|
549
|
+
|
|
550
|
+
opt1 = (opt1 || '').toUpperCase();
|
|
551
|
+
opt2 = (opt2 || '').toUpperCase();
|
|
552
|
+
if (opt1 === 'MATCH') {
|
|
553
|
+
matchPattern = pattern(opt1val);
|
|
554
|
+
if (opt2 === 'COUNT')
|
|
555
|
+
count = str2int(opt2val);
|
|
556
|
+
else if (opt2)
|
|
557
|
+
return BAD_SYNTAX;
|
|
558
|
+
}
|
|
559
|
+
else if (opt1 === 'COUNT') {
|
|
560
|
+
if (opt2)
|
|
561
|
+
return BAD_SYNTAX;
|
|
562
|
+
else
|
|
563
|
+
count = str2int(opt1val);
|
|
564
|
+
}
|
|
565
|
+
else if (opt1)
|
|
566
|
+
return BAD_SYNTAX;
|
|
567
|
+
|
|
568
|
+
if (count instanceof ERROR)
|
|
569
|
+
return count;
|
|
570
|
+
|
|
571
|
+
var nextCursor = cursor + count;
|
|
572
|
+
var keys = allKeys.slice(cursor, nextCursor);
|
|
573
|
+
|
|
574
|
+
// Apply MATCH filtering _after_ getting number of keys
|
|
575
|
+
if (matchPattern) {
|
|
576
|
+
var i = 0;
|
|
577
|
+
while (i < keys.length)
|
|
578
|
+
if (!matchPattern(keys[i]))
|
|
579
|
+
keys.splice(i, size);
|
|
580
|
+
else
|
|
581
|
+
i += size;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Return 0 when iteration is complete.
|
|
585
|
+
if (nextCursor >= allKeys.length)
|
|
586
|
+
nextCursor = 0;
|
|
587
|
+
|
|
588
|
+
return [nextCursor, keys];
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
// String setters.
|
|
593
|
+
|
|
594
|
+
, SET: function () {
|
|
595
|
+
if (arguments.length < 2) return BAD_ARGS;
|
|
596
|
+
var argc = 0;
|
|
597
|
+
var key = arguments[argc++];
|
|
598
|
+
var value = arguments[argc++];
|
|
599
|
+
var buf = new Buffer(Buffer.byteLength(value));
|
|
600
|
+
buf.write(value);
|
|
601
|
+
|
|
602
|
+
var optNX = false;
|
|
603
|
+
var optXX = false;
|
|
604
|
+
var optEX = 0;
|
|
605
|
+
var optPX = 0;
|
|
606
|
+
|
|
607
|
+
// process.stdout.write('arguments is ' + JSON.stringify(arguments));
|
|
608
|
+
while (argc < arguments.length) {
|
|
609
|
+
switch (arguments[argc++].toUpperCase()) {
|
|
610
|
+
case 'NX': {
|
|
611
|
+
optNX = true;
|
|
612
|
+
break;
|
|
613
|
+
}
|
|
614
|
+
case 'XX': {
|
|
615
|
+
optXX = true;
|
|
616
|
+
break;
|
|
617
|
+
}
|
|
618
|
+
case 'EX': {
|
|
619
|
+
if (arguments.length === argc)
|
|
620
|
+
return BAD_ARGS;
|
|
621
|
+
optEX = arguments[argc++];
|
|
622
|
+
/*jshint -W018*/
|
|
623
|
+
if (!(str2int(optEX) > 0))
|
|
624
|
+
return BAD_INT;
|
|
625
|
+
break;
|
|
626
|
+
}
|
|
627
|
+
case 'PX': {
|
|
628
|
+
if (arguments.length === argc)
|
|
629
|
+
return BAD_ARGS;
|
|
630
|
+
optPX = arguments[argc++];
|
|
631
|
+
/*jshint -W018*/
|
|
632
|
+
if (!(str2int(optPX) > 0))
|
|
633
|
+
return BAD_INT;
|
|
634
|
+
break;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
if (optNX) {
|
|
640
|
+
if (this.EXISTS(key)) return null;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
if (optXX) {
|
|
644
|
+
if (!this.EXISTS(key)) return null;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
this.setKey(key, new STRING(buf));
|
|
648
|
+
|
|
649
|
+
if (optEX) {
|
|
650
|
+
this.EXPIRE(key, optEX);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
if (optPX) {
|
|
654
|
+
this.PEXPIRE(key, optPX);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
return OK;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
, sIncrBy: function (parse, key, incr) {
|
|
661
|
+
var K = this.getKey(STRING, key, true);
|
|
662
|
+
if (K instanceof ERROR) return K;
|
|
663
|
+
|
|
664
|
+
incr = parse(incr);
|
|
665
|
+
if (incr instanceof ERROR) return incr;
|
|
666
|
+
var value = parse(K.value.toString() || "0");
|
|
667
|
+
if (value instanceof ERROR) return value;
|
|
668
|
+
|
|
669
|
+
value = (value + incr).toString();
|
|
670
|
+
var buf = new Buffer(Buffer.byteLength(value));
|
|
671
|
+
buf.write(value);
|
|
672
|
+
|
|
673
|
+
K.value = value;
|
|
674
|
+
this.upsetKey(key, K);
|
|
675
|
+
return value;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
, sFit: function (key, length) {
|
|
679
|
+
var K = this.getKey(STRING, key, true);
|
|
680
|
+
if (K instanceof ERROR) return ERROR;
|
|
681
|
+
|
|
682
|
+
if (K.value.length < length) {
|
|
683
|
+
var buf = new Buffer(length);
|
|
684
|
+
buf.fill(0);
|
|
685
|
+
|
|
686
|
+
K.value.copy(buf);
|
|
687
|
+
K.value = buf;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
return K;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
, SETBIT: function (key, offset, state) {
|
|
694
|
+
/*jshint -W018*/
|
|
695
|
+
offset = str2int(offset);
|
|
696
|
+
if (!(offset > - 1)) return BAD_BIT1;
|
|
697
|
+
state = str2int(state);
|
|
698
|
+
if (!(state === 0 || state === 1)) return BAD_BIT2;
|
|
699
|
+
|
|
700
|
+
var x = Math.floor(offset / 8);
|
|
701
|
+
var K = this.sFit(key, x + 1);
|
|
702
|
+
if (K instanceof ERROR) return K;
|
|
703
|
+
|
|
704
|
+
var mask = 1 << (7 - (offset % 8));
|
|
705
|
+
var current = K.value[x];
|
|
706
|
+
var old = current & mask ? 1 : 0;
|
|
707
|
+
|
|
708
|
+
if (state && !old)
|
|
709
|
+
K.value[x] = current | mask;
|
|
710
|
+
else if (!state && old)
|
|
711
|
+
K.value[x] = current & ~mask;
|
|
712
|
+
|
|
713
|
+
this.upsetKey(key, K);
|
|
714
|
+
return old;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
, SETRANGE: function (key, offset, value) {
|
|
718
|
+
/*jshint -W018*/
|
|
719
|
+
offset = str2int(offset);
|
|
720
|
+
if (!(offset > - 1)) return BAD_BIT1;
|
|
721
|
+
|
|
722
|
+
var K = this.sFit(key, offset + Buffer.byteLength(value));
|
|
723
|
+
K.value.write(value, offset);
|
|
724
|
+
|
|
725
|
+
this.upsetKey(key, K);
|
|
726
|
+
return this.STRLEN(key);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
// String getters.
|
|
731
|
+
|
|
732
|
+
, GET: function (key) {
|
|
733
|
+
var K = this.getKey(STRING, key);
|
|
734
|
+
if (K instanceof ERROR) return K;
|
|
735
|
+
return K ? K.value.toString() : null;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
, STRLEN: function (key) {
|
|
739
|
+
var K = this.getKey(STRING, key);
|
|
740
|
+
if (K instanceof ERROR) return ERROR;
|
|
741
|
+
return K ? K.value.length : 0;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
, GETBIT: function (key, offset) {
|
|
745
|
+
/*jshint -W018*/
|
|
746
|
+
var K = this.getKey(STRING, key);
|
|
747
|
+
if (K instanceof ERROR) return ERROR;
|
|
748
|
+
|
|
749
|
+
offset = str2int(offset);
|
|
750
|
+
if (!(offset > - 1)) return BAD_BIT1;
|
|
751
|
+
var x = Math.floor(offset / 8);
|
|
752
|
+
if (!K || K.length < x + 1) return 0;
|
|
753
|
+
|
|
754
|
+
var mask = 1 << (7 - (offset % 8));
|
|
755
|
+
return (K.value[x]& mask)? 1 : 0;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
, GETRANGE: function (key, start, stop) {
|
|
759
|
+
var K = this.getKey(STRING, key);
|
|
760
|
+
if (K instanceof ERROR) return ERROR;
|
|
761
|
+
if (!K) return "";
|
|
762
|
+
|
|
763
|
+
var out = slice(K.value, start, stop);
|
|
764
|
+
if (out instanceof ERROR) return out;
|
|
765
|
+
return out.toString();
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
, BITCOUNT: function () {
|
|
769
|
+
var key = arguments[0];
|
|
770
|
+
var start = arguments[1] || 0;
|
|
771
|
+
var stop = arguments[2] || -1;
|
|
772
|
+
|
|
773
|
+
if (!key) return BAD_ARGS;
|
|
774
|
+
|
|
775
|
+
var K = this.GETRANGE(key, start.toString(), stop.toString());
|
|
776
|
+
if (K instanceof ERROR) return ERROR;
|
|
777
|
+
if (!K) return 0;
|
|
778
|
+
|
|
779
|
+
var bitCount = 0;
|
|
780
|
+
var buff = new Buffer(K);
|
|
781
|
+
for (var i = 0; i < buff.length; i++) {
|
|
782
|
+
bitCount += populationCount(buff[i].toString());
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
return bitCount;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
|
|
789
|
+
// String ops.
|
|
790
|
+
|
|
791
|
+
, APPEND: function (key, value) {
|
|
792
|
+
var strlen = this.STRLEN(key);
|
|
793
|
+
if (strlen instanceof ERROR) return strlen;
|
|
794
|
+
return this.SETRANGE(key, strlen.toString(), value);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
, DECR: function (key) {
|
|
798
|
+
return this.DECRBY(key, "1");
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
, DECRBY: function (key, decr) {
|
|
802
|
+
var value = str2int(decr);
|
|
803
|
+
if (value instanceof ERROR) return value;
|
|
804
|
+
return this.INCRBY(key, ( - value).toString());
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
, GETSET: function (key, value) {
|
|
808
|
+
var old = this.GET(key);
|
|
809
|
+
if (old instanceof ERROR) return old;
|
|
810
|
+
this.SET(key, value);
|
|
811
|
+
return old;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
, INCR: function (key) {
|
|
815
|
+
return this.INCRBY(key, "1");
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
, INCRBY: function (key, incr) {
|
|
819
|
+
return this.sIncrBy(str2int, key, incr);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
, INCRBYFLOAT: function (key, incr) {
|
|
823
|
+
return this.sIncrBy(str2float, key, incr);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
, MGET: function () {
|
|
827
|
+
var out = [], i, n = arguments.length;
|
|
828
|
+
if (!n) return BAD_ARGS;
|
|
829
|
+
|
|
830
|
+
for (i = 0; i < n; i++) {
|
|
831
|
+
var value = this.GET(arguments[i]);
|
|
832
|
+
out[i] = value instanceof ERROR ? null : value;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
return out;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
, MSET: function () {
|
|
839
|
+
var key, value, i, n = arguments.length;
|
|
840
|
+
if (!n || n % 2) return BAD_ARGS;
|
|
841
|
+
|
|
842
|
+
for (i = 0; i < n; i += 2) {
|
|
843
|
+
key = arguments[i];
|
|
844
|
+
value = arguments[i + 1];
|
|
845
|
+
this.SET(key, value);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
return OK;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
, MSETNX: function () {
|
|
852
|
+
var i, n = arguments.length;
|
|
853
|
+
for (i = 0; i < n; i += 2)
|
|
854
|
+
if (this.EXISTS(arguments[i])) return 0;
|
|
855
|
+
|
|
856
|
+
this.MSET.apply(this, arguments);
|
|
857
|
+
return 1;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
, PSETEX: function (key, timediff, value) {
|
|
861
|
+
/*jshint -W018*/
|
|
862
|
+
if (!(str2int(timediff) > 0))
|
|
863
|
+
return BAD_SETEX;
|
|
864
|
+
|
|
865
|
+
this.SET(key, value);
|
|
866
|
+
this.PEXPIRE(key, timediff);
|
|
867
|
+
return OK;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
, SETEX: function (key, timediff, value) {
|
|
871
|
+
/*jshint -W018*/
|
|
872
|
+
if (!(str2int(timediff) > 0))
|
|
873
|
+
return BAD_SETEX;
|
|
874
|
+
|
|
875
|
+
this.SET(key, value);
|
|
876
|
+
this.EXPIRE(key, timediff);
|
|
877
|
+
return OK;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
, SETNX: function (key, value) {
|
|
881
|
+
if (this.EXISTS(key)) return 0;
|
|
882
|
+
this.SET(key, value);
|
|
883
|
+
return 1;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
|
|
887
|
+
// Lists, non-blocking.
|
|
888
|
+
|
|
889
|
+
, lStore: function (key, values) {
|
|
890
|
+
// Only used in SORT.
|
|
891
|
+
|
|
892
|
+
if (values.length)
|
|
893
|
+
return this.setKey(key, new LIST(values));
|
|
894
|
+
else
|
|
895
|
+
return this.setKey(key, null);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
, LINDEX: function (key, index) {
|
|
899
|
+
var K = this.getKey(LIST, key);
|
|
900
|
+
if (K instanceof ERROR) return K;
|
|
901
|
+
|
|
902
|
+
index = str2int(index);
|
|
903
|
+
if (index instanceof ERROR)
|
|
904
|
+
return index;
|
|
905
|
+
|
|
906
|
+
return (K && K.value[index < 0 ? K.value.length + index : index]) || null;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
, upsetList: function (key, K) {
|
|
910
|
+
if (K.value.length) this.upsetKey(key, K);
|
|
911
|
+
else this.setKey(key, null);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
, LINSERT: function (key, relpos, pivot, value) {
|
|
915
|
+
var K = this.getKey(LIST, key), x;
|
|
916
|
+
if (K instanceof ERROR) return K;
|
|
917
|
+
|
|
918
|
+
relpos = relpos.toUpperCase();
|
|
919
|
+
if (relpos !== 'BEFORE' && relpos !== 'AFTER') return BAD_SYNTAX;
|
|
920
|
+
if (!K) return 0;
|
|
921
|
+
if ((x = K.value.indexOf(pivot)) < 0) return 0;
|
|
922
|
+
|
|
923
|
+
K.value.splice(relpos === 'AFTER'? x + 1 : x, 0, value);
|
|
924
|
+
this.upsetList(key, K);
|
|
925
|
+
return 1;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
, LLEN: function (key) {
|
|
929
|
+
var K = this.getKey(LIST, key);
|
|
930
|
+
if (K instanceof ERROR) return K;
|
|
931
|
+
|
|
932
|
+
return (K && K.value && K.value.length) || 0;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
, lPopMany: function (left, keys) {
|
|
936
|
+
var K = [], value, i, n = keys.length;
|
|
937
|
+
if (!n) return BAD_ARGS;
|
|
938
|
+
for (i = 0; i < n; i++) {
|
|
939
|
+
K[i] = this.getKey(LIST, keys[i]);
|
|
940
|
+
if (K[i]instanceof ERROR) return K[i];
|
|
941
|
+
}
|
|
942
|
+
for (i = 0; i < n; i++)
|
|
943
|
+
if (K[i] && K[i].value && K[i].value.length) {
|
|
944
|
+
value = left ? K[i].value.shift() : K[i].value.pop();
|
|
945
|
+
this.upsetList(keys[i], K[i]);
|
|
946
|
+
return [keys[i], value];
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
return null;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
, lPop: function (left, key) {
|
|
953
|
+
var out = this.lPopMany(left, [key]);
|
|
954
|
+
return out && out.length ? out[1] : out;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
, LPOP: function (key) {
|
|
958
|
+
return this.lPop(true, key);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
, RPOP: function (key) {
|
|
962
|
+
return this.lPop(false, key);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
, lPush: function (left, make, args) {
|
|
966
|
+
var i, n = args.length, key = args[0];
|
|
967
|
+
var K = this.getKey(LIST, key, make);
|
|
968
|
+
if (K instanceof ERROR) return K;
|
|
969
|
+
if (n < 2) return BAD_ARGS;
|
|
970
|
+
if (!K) return 0;
|
|
971
|
+
|
|
972
|
+
if (left) for (i = 1; i < n; i++)
|
|
973
|
+
K.value.unshift(args[i]);
|
|
974
|
+
else
|
|
975
|
+
K.value.push.apply(K.value, args.slice(1));
|
|
976
|
+
|
|
977
|
+
this.upsetList(key, K);
|
|
978
|
+
return K.value.length;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
, LPUSH: function () {
|
|
982
|
+
return this.lPush(true, true, arr(arguments));
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
, LPUSHX: function () {
|
|
986
|
+
return this.lPush(true, false, arr(arguments));
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
, RPUSH: function () {
|
|
990
|
+
return this.lPush(false, true, arr(arguments));
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
, RPUSHX: function () {
|
|
994
|
+
return this.lPush(false, false, arr(arguments));
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
, RPOPLPUSH: function (source, destination) {
|
|
998
|
+
var dest = this.getKey(LIST, destination);
|
|
999
|
+
if (dest && dest instanceof ERROR) return dest;
|
|
1000
|
+
var value = this.RPOP(source);
|
|
1001
|
+
if (value === null || value instanceof ERROR) return value;
|
|
1002
|
+
|
|
1003
|
+
var len = this.LPUSH(destination, value);
|
|
1004
|
+
if (!len || len instanceof ERROR) throw new Error("WOOT! LPUSH failed in RPOPLPUSH.");
|
|
1005
|
+
|
|
1006
|
+
return value;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
, LRANGE: function (key, start, stop) {
|
|
1010
|
+
var K = this.getKey(LIST, key);
|
|
1011
|
+
if (K instanceof ERROR) return K;
|
|
1012
|
+
if (!K) return [];
|
|
1013
|
+
|
|
1014
|
+
return slice(K.value, start, stop);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
, LREM: function (key, count, value) {
|
|
1018
|
+
var K = this.getKey(LIST, key);
|
|
1019
|
+
if (K instanceof ERROR) return K;
|
|
1020
|
+
count = str2int(count);
|
|
1021
|
+
if (count instanceof ERROR) return count;
|
|
1022
|
+
if (!K) return 0;
|
|
1023
|
+
|
|
1024
|
+
var i, n = K.value.length, x = 0;
|
|
1025
|
+
if (count < 0) {
|
|
1026
|
+
count *= - 1;
|
|
1027
|
+
for (i = n - 1; i >= 0; i--)
|
|
1028
|
+
if (K.value[i] === value && (!count || x < count)) {
|
|
1029
|
+
K.value.splice(i, 1);
|
|
1030
|
+
x++;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
else for (i = 0; i < n; i++)
|
|
1034
|
+
if (K.value[i] === value && (!count || x < count)) {
|
|
1035
|
+
K.value.splice(i, 1);
|
|
1036
|
+
i--; n--; x++;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
if (x > 0) this.upsetList(key, K);
|
|
1040
|
+
return x;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
, LSET: function (key, index, value) {
|
|
1044
|
+
var K = this.getKey(LIST, key);
|
|
1045
|
+
if (!K) return BAD_KEY;
|
|
1046
|
+
if (K instanceof ERROR) return K;
|
|
1047
|
+
index = str2int(index);
|
|
1048
|
+
if (index instanceof ERROR) return index;
|
|
1049
|
+
if (index < 0 || index > K.value.length) return BAD_INDEX;
|
|
1050
|
+
|
|
1051
|
+
K.value[index] = value;
|
|
1052
|
+
this.upsetList(key, K);
|
|
1053
|
+
return OK;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
, LTRIM: function (key, start, stop) {
|
|
1057
|
+
var range = this.LRANGE(key, start, stop);
|
|
1058
|
+
if (!range.join)
|
|
1059
|
+
return range;
|
|
1060
|
+
|
|
1061
|
+
var K = this.getKey(LIST, key);
|
|
1062
|
+
if (K) {
|
|
1063
|
+
K.value = range;
|
|
1064
|
+
this.upsetList(key, K);
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
return OK;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
|
|
1071
|
+
// Blocking list commands.
|
|
1072
|
+
// The blocking part happens at the connection level,
|
|
1073
|
+
// where in case the response is null the connection subscribes to the keyspace change event for the key and waits to retry.
|
|
1074
|
+
|
|
1075
|
+
// So this only validates the parameter.
|
|
1076
|
+
|
|
1077
|
+
, bArgs: function (args) {
|
|
1078
|
+
args = arr(args);
|
|
1079
|
+
var timeout = str2int(args.pop() || "FAIL");
|
|
1080
|
+
if (timeout instanceof ERROR) return timeout;
|
|
1081
|
+
if (timeout < 0) return BAD_INT;
|
|
1082
|
+
return args;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
, BLPOP: function () {
|
|
1086
|
+
var a = this.bArgs(arguments);
|
|
1087
|
+
if (a instanceof ERROR) return a;
|
|
1088
|
+
return this.lPopMany(true, a);
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
, BRPOP: function () {
|
|
1092
|
+
var a = this.bArgs(arguments);
|
|
1093
|
+
if (a instanceof ERROR) return a;
|
|
1094
|
+
return this.lPopMany(false, a);
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
, BRPOPLPUSH: function () {
|
|
1098
|
+
var a = this.bArgs(arguments);
|
|
1099
|
+
if (a instanceof ERROR) return a;
|
|
1100
|
+
return this.RPOPLPUSH.apply(this, a);
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
|
|
1104
|
+
// Hashes.
|
|
1105
|
+
|
|
1106
|
+
, structPut: function (type, validate, revArgs, args) {
|
|
1107
|
+
var key = args[0], i, n = args.length, x = 0;
|
|
1108
|
+
|
|
1109
|
+
if (n < 3 || ((n - 1) % 2)) return BAD_ARGS;
|
|
1110
|
+
var K = this.getKey(type, key, true);
|
|
1111
|
+
if (K instanceof ERROR) return K;
|
|
1112
|
+
|
|
1113
|
+
for (i = 1; i < n; i += 2) {
|
|
1114
|
+
var member = args[revArgs ? i + 1 : i]
|
|
1115
|
+
, value = validate(args[revArgs ? i : i + 1]);
|
|
1116
|
+
if (value instanceof ERROR) return value;
|
|
1117
|
+
if (!(member in K.value)) x++;
|
|
1118
|
+
K.value[member] = value;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
if (x) this.upsetKey(key, K);
|
|
1122
|
+
return x;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
, structDel: function (type, args) {
|
|
1126
|
+
var key = args[0]
|
|
1127
|
+
, i, n = args.length
|
|
1128
|
+
, x;
|
|
1129
|
+
|
|
1130
|
+
if (n < 2) return BAD_ARGS;
|
|
1131
|
+
var K = this.getKey(type, key);
|
|
1132
|
+
if (K instanceof ERROR) return K;
|
|
1133
|
+
if (!K) return 0;
|
|
1134
|
+
|
|
1135
|
+
x = 0;
|
|
1136
|
+
for (i = 1; i < n; i++) {
|
|
1137
|
+
if (args[i]in K.value) x++;
|
|
1138
|
+
delete K.value[args[i]];
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
// Remove the set if empty, upset otherwise.
|
|
1142
|
+
|
|
1143
|
+
var member;
|
|
1144
|
+
for (member in K.value) {
|
|
1145
|
+
if (x) this.upsetKey(key, K);
|
|
1146
|
+
return x;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
this.setKey(key, null);
|
|
1150
|
+
return x;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
, structGet: function (type, key, member) {
|
|
1154
|
+
var K = this.getKey(type, key);
|
|
1155
|
+
if (K instanceof ERROR) return K;
|
|
1156
|
+
if (!K || !(member in K.value)) return null;
|
|
1157
|
+
return K.value[member];
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
, HDEL: function () {
|
|
1161
|
+
return this.structDel(HASH, arguments);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
, HEXISTS: function (key, field) {
|
|
1165
|
+
var fields = this.HKEYS(key);
|
|
1166
|
+
if (fields.indexOf) return fields.indexOf(field) >= 0 ? 1 : 0;
|
|
1167
|
+
return fields;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
, HGET: function (key, field) {
|
|
1171
|
+
return this.structGet(HASH, key, field);
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
, HGETALL: function (key) {
|
|
1175
|
+
var fields = this.HKEYS(key);
|
|
1176
|
+
var i, n = fields.length, out = [];
|
|
1177
|
+
for (i = 0; i < n; i++)
|
|
1178
|
+
out.push(fields[i], this.HGET(key, fields[i]));
|
|
1179
|
+
|
|
1180
|
+
return out;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
, hIncrBy: function (parse, key, field, incr) {
|
|
1184
|
+
var K = this.getKey(HASH, key, true);
|
|
1185
|
+
if (K instanceof ERROR) return K;
|
|
1186
|
+
|
|
1187
|
+
incr = parse(incr);
|
|
1188
|
+
if (incr instanceof ERROR) return incr;
|
|
1189
|
+
var value = parse(K.value[field] || "0");
|
|
1190
|
+
if (value instanceof ERROR) return value;
|
|
1191
|
+
|
|
1192
|
+
K.value[field] = (value + incr).toString();
|
|
1193
|
+
this.upsetKey(key, K);
|
|
1194
|
+
return K.value[field];
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
, HINCRBY: function (key, field, incr) {
|
|
1198
|
+
return this.hIncrBy(str2int, key, field, incr);
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
, HINCRBYFLOAT: function (key, field, incr) {
|
|
1202
|
+
return this.hIncrBy(str2float, key, field, incr);
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
, HKEYS: function (key) {
|
|
1206
|
+
var K = this.getKey(HASH, key);
|
|
1207
|
+
if (K instanceof ERROR) return K;
|
|
1208
|
+
|
|
1209
|
+
var fields = [], field;
|
|
1210
|
+
if (K) for (field in K.value)
|
|
1211
|
+
fields.push(field);
|
|
1212
|
+
|
|
1213
|
+
fields.sort();
|
|
1214
|
+
return fields;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
, HLEN: function (key) {
|
|
1218
|
+
var fields = this.HKEYS(key);
|
|
1219
|
+
if (fields.indexOf) return fields.length;
|
|
1220
|
+
return fields;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
, HMGET: function () {
|
|
1224
|
+
var K = this.getKey(HASH, arguments[0]);
|
|
1225
|
+
if (K instanceof ERROR) return K;
|
|
1226
|
+
|
|
1227
|
+
var i, n = arguments.length, values = [];
|
|
1228
|
+
if (n < 2) return BAD_ARGS;
|
|
1229
|
+
for (i = 1; i < n; i++)
|
|
1230
|
+
values.push(K && arguments[i]in K.value ? K.value[arguments[i]] : null);
|
|
1231
|
+
|
|
1232
|
+
return values;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
, HMSET: function () {
|
|
1236
|
+
var x = this.structPut(HASH, String, false, arguments);
|
|
1237
|
+
return x instanceof ERROR ? x : OK;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
, HSET: function () {
|
|
1241
|
+
return this.structPut(HASH, String, false, arguments);
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
, HSETNX: function (key, field, value) {
|
|
1245
|
+
var exists = this.HEXISTS(key, field);
|
|
1246
|
+
if (exists instanceof ERROR) return exists;
|
|
1247
|
+
if (exists) return 0;
|
|
1248
|
+
return this.HSET(key, field, value);
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
, HVALS: function (key) {
|
|
1252
|
+
var out = this.HKEYS(key), self = this;
|
|
1253
|
+
if (out instanceof ERROR) return out;
|
|
1254
|
+
|
|
1255
|
+
if (out.map)
|
|
1256
|
+
out = out.map(function (field) {
|
|
1257
|
+
return self.HGET(key, field);
|
|
1258
|
+
});
|
|
1259
|
+
|
|
1260
|
+
out.sort();
|
|
1261
|
+
return out;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
|
|
1265
|
+
// Sets.
|
|
1266
|
+
|
|
1267
|
+
, SADD: function () {
|
|
1268
|
+
var key = arguments[0]
|
|
1269
|
+
, i, n = arguments.length
|
|
1270
|
+
, x = 0;
|
|
1271
|
+
|
|
1272
|
+
if (n < 2) return BAD_ARGS;
|
|
1273
|
+
var K = this.getKey(SET, key, true);
|
|
1274
|
+
if (K instanceof ERROR) return K;
|
|
1275
|
+
|
|
1276
|
+
for (i = 1; i < n; i++)
|
|
1277
|
+
if (!K.value[arguments[i]]) {
|
|
1278
|
+
K.value[arguments[i]] = true;
|
|
1279
|
+
x++;
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
if (x) this.upsetKey(key, K);
|
|
1283
|
+
return x;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
, SCARD: function (key) {
|
|
1287
|
+
var members = this.SMEMBERS(key);
|
|
1288
|
+
return members.join ? members.length : members;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
, SISMEMBER: function (key, member) {
|
|
1292
|
+
var members = this.SMEMBERS(key);
|
|
1293
|
+
return members.indexOf ? members.indexOf(member) >= 0 ? 1 : 0: members;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
, SMEMBERS: function (key) {
|
|
1297
|
+
return this.SUNION(key);
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
, SPOP: function (key) {
|
|
1301
|
+
var member = this.SRANDMEMBER(key);
|
|
1302
|
+
if (typeof member === 'string')
|
|
1303
|
+
this.SREM(key, member);
|
|
1304
|
+
|
|
1305
|
+
return member;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
, SRANDMEMBER: function (key) {
|
|
1309
|
+
var members = this.SMEMBERS(key)
|
|
1310
|
+
, n = members.length, member;
|
|
1311
|
+
if (!n)
|
|
1312
|
+
return n === 0 ? null : members;
|
|
1313
|
+
|
|
1314
|
+
member = members[Math.floor(Math.random()* n)];
|
|
1315
|
+
return member;
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
, SREM: function () {
|
|
1319
|
+
return this.structDel(SET, arguments);
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
|
|
1323
|
+
// Set multikey ops.
|
|
1324
|
+
// Set members come out sorted lexicographically to facilitate testing.
|
|
1325
|
+
|
|
1326
|
+
, SMOVE: function (source, destination, member) {
|
|
1327
|
+
var removed = this.SREM(source, member);
|
|
1328
|
+
if (removed === 1)
|
|
1329
|
+
return this.SADD(destination, member);
|
|
1330
|
+
else
|
|
1331
|
+
return removed;
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
, SUNION: function () {
|
|
1335
|
+
var i, n = arguments.length, out = [];
|
|
1336
|
+
if (!n) return BAD_ARGS;
|
|
1337
|
+
|
|
1338
|
+
for (i = 0; i < n; i++) {
|
|
1339
|
+
var K = this.getKey(SET, arguments[i]);
|
|
1340
|
+
if (K instanceof ERROR) return K;
|
|
1341
|
+
|
|
1342
|
+
var member;
|
|
1343
|
+
if (K) for (member in K.value)
|
|
1344
|
+
if (out.indexOf(member) < 0)
|
|
1345
|
+
out.push(member);
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
out.sort();
|
|
1349
|
+
return out;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
, sCombine: function (diff, args) {
|
|
1353
|
+
var i, n = args.length;
|
|
1354
|
+
if (!n)
|
|
1355
|
+
return BAD_ARGS;
|
|
1356
|
+
|
|
1357
|
+
var out = this.SUNION(args[0]);
|
|
1358
|
+
if (out instanceof ERROR) return out;
|
|
1359
|
+
for (i = 1; i < n; i++) {
|
|
1360
|
+
var K = this.getKey(SET, args[i]);
|
|
1361
|
+
if (K instanceof ERROR) return K;
|
|
1362
|
+
|
|
1363
|
+
var j, m = out.length;
|
|
1364
|
+
for (j = 0; j < m; j++)
|
|
1365
|
+
if ((diff && (K && K.value[out[j]])) || (!diff && !(K && K.value[out[j]]))) {
|
|
1366
|
+
out.splice(j, 1);
|
|
1367
|
+
j--;
|
|
1368
|
+
m--;
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
out.sort();
|
|
1373
|
+
return out;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
, SDIFF: function () {
|
|
1377
|
+
return this.sCombine(true, arr(arguments));
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
, SINTER: function () {
|
|
1381
|
+
return this.sCombine(false, arr(arguments));
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
, sStore: function (key, members) {
|
|
1385
|
+
var K, i, n = members.length;
|
|
1386
|
+
if (n) K = new SET({});
|
|
1387
|
+
for (i = 0; i < n; i++)
|
|
1388
|
+
K.value[members[i]] = true;
|
|
1389
|
+
|
|
1390
|
+
return this.setKey(key, K || null);
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
, sStoreOp: function (op, args) {
|
|
1394
|
+
if (!args.length)
|
|
1395
|
+
return BAD_ARGS;
|
|
1396
|
+
|
|
1397
|
+
var key = args.shift()
|
|
1398
|
+
, members = op.apply(this, args);
|
|
1399
|
+
|
|
1400
|
+
if (members.join) {
|
|
1401
|
+
this.sStore(key, members);
|
|
1402
|
+
return members.length;
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
return members;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
, SDIFFSTORE: function () {
|
|
1409
|
+
return this.sStoreOp(this.SDIFF, arr(arguments));
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
, SINTERSTORE: function () {
|
|
1413
|
+
return this.sStoreOp(this.SINTER, arr(arguments));
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
, SUNIONSTORE: function () {
|
|
1417
|
+
return this.sStoreOp(this.SUNION, arr(arguments));
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
|
|
1421
|
+
// Sorted sets.
|
|
1422
|
+
|
|
1423
|
+
, ZADD: function () {
|
|
1424
|
+
return this.structPut(ZSET, str2float, true, arguments);
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
, ZCARD: function (key) {
|
|
1428
|
+
return this.ZCOUNT(key, '-inf', '+inf');
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
, ZCOUNT: function (key, min, max) {
|
|
1432
|
+
var members = this.ZRANGEBYSCORE(key, min, max);
|
|
1433
|
+
return members.join ? members.length : members;
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
, ZINCRBY: function (key, incr, member) {
|
|
1437
|
+
var K = this.getKey(ZSET, key, true);
|
|
1438
|
+
if (K instanceof ERROR) return K;
|
|
1439
|
+
|
|
1440
|
+
var value = str2float(incr);
|
|
1441
|
+
if (value instanceof ERROR) return value;
|
|
1442
|
+
value += Number(K.value[member] || 0);
|
|
1443
|
+
|
|
1444
|
+
K.value[member] = value;
|
|
1445
|
+
this.upsetKey(key, K);
|
|
1446
|
+
return value;
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
|
|
1450
|
+
// Sort set queries.
|
|
1451
|
+
|
|
1452
|
+
, zSort: function (rev, key, min, max) {
|
|
1453
|
+
var K = this.getKey(ZSET, key);
|
|
1454
|
+
if (K instanceof ERROR) return K;
|
|
1455
|
+
if (!K) return [];
|
|
1456
|
+
|
|
1457
|
+
var rng = range(min, max), member, out = [];
|
|
1458
|
+
if (rng instanceof ERROR) return rng;
|
|
1459
|
+
|
|
1460
|
+
for (member in K.value)
|
|
1461
|
+
if (rng(K.value[member]))
|
|
1462
|
+
out.push({ member: member, score: K.value[member] });
|
|
1463
|
+
|
|
1464
|
+
// First by score,
|
|
1465
|
+
// then in lexicographic order.
|
|
1466
|
+
|
|
1467
|
+
if (rev)
|
|
1468
|
+
out.sort(function (b, a) {
|
|
1469
|
+
return (a.score - b.score) || (a.member < b.member ? - 1 : 1);
|
|
1470
|
+
});
|
|
1471
|
+
|
|
1472
|
+
else
|
|
1473
|
+
out.sort(function (a, b) {
|
|
1474
|
+
return (a.score - b.score) || (a.member < b.member ? - 1 : 1);
|
|
1475
|
+
});
|
|
1476
|
+
|
|
1477
|
+
return out;
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
, zUnwrap: function (range, scores) {
|
|
1481
|
+
var i, n = range.length, out = n ?[] : range;
|
|
1482
|
+
if (n)
|
|
1483
|
+
for (i = 0; i < n; i++) {
|
|
1484
|
+
out.push(range[i].member);
|
|
1485
|
+
if (scores)
|
|
1486
|
+
out.push(range[i].score);
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
return out;
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
, zGetRange: function (rev, args) {
|
|
1493
|
+
var key = args[0], start = args[1], stop = args[2], scores = args[3];
|
|
1494
|
+
|
|
1495
|
+
if (args.length < 3 || args.length > 4)
|
|
1496
|
+
return BAD_ARGS;
|
|
1497
|
+
if (scores && scores.toUpperCase() !== 'WITHSCORES')
|
|
1498
|
+
return BAD_SYNTAX;
|
|
1499
|
+
|
|
1500
|
+
var range = this.zSort(rev, key, '-inf', '+inf');
|
|
1501
|
+
|
|
1502
|
+
return this.zUnwrap(slice(range, start, stop), scores);
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
, zGetRangeByScore: function (rev, args) {
|
|
1506
|
+
var key = args[0], min = args[rev ? 2 : 1], max = args[rev ? 1 : 2]
|
|
1507
|
+
, scores, limit, offset, count;
|
|
1508
|
+
|
|
1509
|
+
if (args.length < 3)
|
|
1510
|
+
return BAD_ARGS;
|
|
1511
|
+
|
|
1512
|
+
else if (args.length === 4)
|
|
1513
|
+
scores = args[3];
|
|
1514
|
+
|
|
1515
|
+
else if (args.length === 6) {
|
|
1516
|
+
limit = args[3];
|
|
1517
|
+
offset = args[4];
|
|
1518
|
+
count = args[5];
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
else if (args.length === 7) {
|
|
1522
|
+
scores = args[3];
|
|
1523
|
+
limit = args[4];
|
|
1524
|
+
offset = args[5];
|
|
1525
|
+
count = args[6];
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
if (scores && scores.toUpperCase() !== 'WITHSCORES')
|
|
1529
|
+
return BAD_SYNTAX;
|
|
1530
|
+
if (limit && limit.toUpperCase() !== 'LIMIT')
|
|
1531
|
+
return BAD_SYNTAX;
|
|
1532
|
+
|
|
1533
|
+
var range = this.zSort(rev, key, min, max);
|
|
1534
|
+
if (limit)
|
|
1535
|
+
range = slice(range, offset, count, true);
|
|
1536
|
+
|
|
1537
|
+
return this.zUnwrap(range, scores);
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
, ZRANGE: function () {
|
|
1541
|
+
return this.zGetRange(false, arr(arguments));
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
, ZREVRANGE: function () {
|
|
1545
|
+
return this.zGetRange(true, arr(arguments));
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
, ZRANGEBYSCORE: function () {
|
|
1549
|
+
return this.zGetRangeByScore(false, arr(arguments));
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
, ZREVRANGEBYSCORE: function () {
|
|
1553
|
+
return this.zGetRangeByScore(true, arr(arguments));
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
, ZRANK: function (key, member) {
|
|
1557
|
+
var out = this.zSort(false, key, '-inf', '+inf')
|
|
1558
|
+
, i, n = out.length;
|
|
1559
|
+
|
|
1560
|
+
for (i = 0; i < n; i++)
|
|
1561
|
+
if (out[i].member === member)
|
|
1562
|
+
return i;
|
|
1563
|
+
|
|
1564
|
+
return n || n === 0 ? null : out;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
, ZREVRANK: function (key, member) {
|
|
1568
|
+
var out = this.zSort(false, key, '-inf', '+inf')
|
|
1569
|
+
, i, n = out.length;
|
|
1570
|
+
|
|
1571
|
+
for (i = n - 1; i >= 0; i--)
|
|
1572
|
+
if (out[i].member === member)
|
|
1573
|
+
return n - i - 1;
|
|
1574
|
+
|
|
1575
|
+
return n || n === 0 ? null : out;
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
, ZSCORE: function (key, member) {
|
|
1579
|
+
return this.structGet(ZSET, key, member);
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
, ZREM: function () {
|
|
1583
|
+
return this.structDel(ZSET, arguments);
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
, ZREMRANGEBYRANK: function (key, start, stop) {
|
|
1587
|
+
var members = this.ZRANGE(key, start, stop), n = members.length;
|
|
1588
|
+
if (n)
|
|
1589
|
+
n = this.ZREM.apply(this, [key].concat(members));
|
|
1590
|
+
|
|
1591
|
+
return n || n === 0 ? n : members;
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
, ZREMRANGEBYSCORE: function (key, min, max) {
|
|
1595
|
+
var members = this.ZRANGEBYSCORE(key, min, max), n = members.length;
|
|
1596
|
+
if (n)
|
|
1597
|
+
n = this.ZREM.apply(this, [key].concat(members));
|
|
1598
|
+
|
|
1599
|
+
return n || n === 0 ? n : members;
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
|
|
1603
|
+
// Sorted set multikey ops.
|
|
1604
|
+
|
|
1605
|
+
, getSetOrZsetKey: function (key) {
|
|
1606
|
+
var K;
|
|
1607
|
+
var type = this.TYPE(key);
|
|
1608
|
+
if (type === ZSET || type === SET) {
|
|
1609
|
+
K = this.getKey(type, key);
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
return K;
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
|
|
1616
|
+
, zOpStore: function (union, key, keys, weights, aggregate) {
|
|
1617
|
+
var K = this.getSetOrZsetKey(keys[0]);
|
|
1618
|
+
if (K instanceof ERROR) return K;
|
|
1619
|
+
|
|
1620
|
+
var out = {}, member, x = 0, weight = (weights === null ? 1 : weights[0]);
|
|
1621
|
+
if (K) for (member in K.value) {
|
|
1622
|
+
out[member] = K.value[member]* weight;
|
|
1623
|
+
x++;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
var i, n = keys.length;
|
|
1627
|
+
for (i = 1; i < n; i++) {
|
|
1628
|
+
K = this.getSetOrZsetKey(keys[i]);
|
|
1629
|
+
if (K instanceof ERROR) return K;
|
|
1630
|
+
|
|
1631
|
+
weight = (weights !== null ? weights[i] : 1);
|
|
1632
|
+
if (!union) {
|
|
1633
|
+
if (!K) {
|
|
1634
|
+
out = {};
|
|
1635
|
+
x = 0;
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
else for (member in out) if (!(member in K.value)) {
|
|
1639
|
+
delete out[member];
|
|
1640
|
+
x--;
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
if (K) for (member in K.value)
|
|
1645
|
+
if (union || member in out) {
|
|
1646
|
+
if (!(member in out)) {
|
|
1647
|
+
x++;
|
|
1648
|
+
out[member] = K.value[member]* weight;
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
else
|
|
1652
|
+
out[member] = aggregate(K.value[member]* weight, out[member]);
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
if (x) this.setKey(key, new ZSET(out));
|
|
1657
|
+
return x;
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
, zsum: function (a, b) { return a + b; }
|
|
1661
|
+
, zmin: function (a, b) { return a < b ? a : b; }
|
|
1662
|
+
, zmax: function (a, b) { return a > b ? a : b; }
|
|
1663
|
+
|
|
1664
|
+
, zParseOpStore: function (union, args) {
|
|
1665
|
+
var key = args[0], N = str2int(args[1]);
|
|
1666
|
+
if (N instanceof ERROR) return N;
|
|
1667
|
+
if (N < 1) return BAD_ZUIS;
|
|
1668
|
+
if (args.length < N + 2) return BAD_ARGS;
|
|
1669
|
+
|
|
1670
|
+
var keys = args.splice(2, N), weigh = (args[2] || '').toUpperCase() === 'WEIGHTS', weights;
|
|
1671
|
+
if (weigh) {
|
|
1672
|
+
if (args.length < N + 3) return BAD_ARGS;
|
|
1673
|
+
weights = args.splice(3, N);
|
|
1674
|
+
if (weights.map(str2float).some(function (w) { return w instanceof ERROR; })) return BAD_FLOAT;
|
|
1675
|
+
args.splice(2, 1);
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
var aggregate = (args[2] || '').toUpperCase() === 'AGGREGATE' ? (args[3] || '').toLowerCase() : null;
|
|
1679
|
+
if (aggregate) {
|
|
1680
|
+
if (aggregate !== 'sum' && aggregate !== 'min' && aggregate !== 'max') return BAD_SYNTAX;
|
|
1681
|
+
aggregate = this['z' + aggregate];
|
|
1682
|
+
if (typeof aggregate !== 'function')
|
|
1683
|
+
throw new Error("WOOT! Can't find the aggregate function for " + args[3]);
|
|
1684
|
+
args.splice(2, 2);
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
if (args.length !== 2)
|
|
1688
|
+
return BAD_ARGS;
|
|
1689
|
+
|
|
1690
|
+
return this.zOpStore(union, key, keys, weights || null, aggregate || this.zsum);
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
, ZINTERSTORE: function () {
|
|
1694
|
+
return this.zParseOpStore(false, arr(arguments));
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
, ZUNIONSTORE: function () {
|
|
1698
|
+
return this.zParseOpStore(true, arr(arguments));
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
|
|
1702
|
+
// Sort.
|
|
1703
|
+
|
|
1704
|
+
, sortSelect: function (pat, kkey) {
|
|
1705
|
+
var select = /^((?:.)*?)(?:->(.*))?$/.exec(pat)
|
|
1706
|
+
, key = select[1].replace(/\*/, kkey), // no g flag, so only first occurence is replaced
|
|
1707
|
+
field = select[2];
|
|
1708
|
+
|
|
1709
|
+
if (typeof field === 'string')
|
|
1710
|
+
return this.HGET(key, field);
|
|
1711
|
+
else
|
|
1712
|
+
return this.GET(key);
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
, SORT: function () {
|
|
1716
|
+
var self = this, args = arr(arguments), n = args.length;
|
|
1717
|
+
if (!n) return new BAD_ARGS;
|
|
1718
|
+
|
|
1719
|
+
// Parse.
|
|
1720
|
+
// SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination]
|
|
1721
|
+
|
|
1722
|
+
var key = args.shift()
|
|
1723
|
+
, by, limit, offset, count, get, pat, desc, alpha, store
|
|
1724
|
+
, seenAscDesc = false;
|
|
1725
|
+
|
|
1726
|
+
if (/^by$/i.test(args[0])) {
|
|
1727
|
+
by = args[1];
|
|
1728
|
+
if (typeof by !== 'string') return BAD_SYNTAX;
|
|
1729
|
+
args.splice(0, 2);
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
if (/^limit$/i.test(args[0])) {
|
|
1733
|
+
limit = true;
|
|
1734
|
+
if (args.length < 3) return BAD_ARGS;
|
|
1735
|
+
offset = args[1]; // integer validation happens in slice()
|
|
1736
|
+
count = args[2];
|
|
1737
|
+
args.splice(0, 3);
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
if (/^asc|desc$/i.test(args[0])) {
|
|
1741
|
+
desc = /^desc$/i.test(args[0]);
|
|
1742
|
+
args.splice(0, 1);
|
|
1743
|
+
seenAscDesc = true;
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
while (/^get$/i.test(args[0])) {
|
|
1747
|
+
pat = args[1];
|
|
1748
|
+
if (typeof pat !== 'string') return BAD_SYNTAX;
|
|
1749
|
+
if (!get) get = [];
|
|
1750
|
+
get.push(pat);
|
|
1751
|
+
args.splice(0, 2);
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
if (!seenAscDesc)
|
|
1755
|
+
if (/^asc|desc$/i.test(args[0])) {
|
|
1756
|
+
desc = /^desc$/i.test(args[0]);
|
|
1757
|
+
args.splice(0, 1);
|
|
1758
|
+
seenAscDesc = true;
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
if (/^alpha$/i.test(args[0])) {
|
|
1762
|
+
alpha = true;
|
|
1763
|
+
args.splice(0, 1);
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
if (/^store$/i.test(args[0])) {
|
|
1767
|
+
store = args[1];
|
|
1768
|
+
if (typeof store !== 'string') return BAD_SYNTAX;
|
|
1769
|
+
args.splice(0, 2);
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
// Redis appears to accept params in any order,
|
|
1773
|
+
// needs some tests before allowing this here.
|
|
1774
|
+
|
|
1775
|
+
if (args.length) return BAD_SYNTAX;
|
|
1776
|
+
|
|
1777
|
+
// Collect data.
|
|
1778
|
+
|
|
1779
|
+
var type = this.TYPE(key), data, scoreFail = false;
|
|
1780
|
+
|
|
1781
|
+
if (type === NONE)
|
|
1782
|
+
data = [];
|
|
1783
|
+
else if (type === LIST)
|
|
1784
|
+
data = this.LRANGE(key, '0', '-1');
|
|
1785
|
+
else if (type === SET)
|
|
1786
|
+
data = this.SMEMBERS(key);
|
|
1787
|
+
else if (type === ZSET)
|
|
1788
|
+
data = this.ZRANGE(key, '0', '-1');
|
|
1789
|
+
else
|
|
1790
|
+
return BAD_TYPE;
|
|
1791
|
+
|
|
1792
|
+
data = data.map(function (id) {
|
|
1793
|
+
var entry = { id: id };
|
|
1794
|
+
if (by) {
|
|
1795
|
+
entry.by = self.sortSelect(by, id);
|
|
1796
|
+
if (!alpha)
|
|
1797
|
+
entry.num = str2float(entry.by || '0');
|
|
1798
|
+
}
|
|
1799
|
+
else if (!alpha)
|
|
1800
|
+
entry.num = str2float(id);
|
|
1801
|
+
else
|
|
1802
|
+
entry.num = 0;
|
|
1803
|
+
|
|
1804
|
+
if (entry.num instanceof ERROR)
|
|
1805
|
+
scoreFail = true;
|
|
1806
|
+
|
|
1807
|
+
if (get)
|
|
1808
|
+
entry.get = get.map(function (get) {
|
|
1809
|
+
if (get === '#') return id;
|
|
1810
|
+
return self.sortSelect(get, id);
|
|
1811
|
+
});
|
|
1812
|
+
|
|
1813
|
+
return entry;
|
|
1814
|
+
});
|
|
1815
|
+
|
|
1816
|
+
if (scoreFail) return BAD_SORT;
|
|
1817
|
+
|
|
1818
|
+
// Sort.
|
|
1819
|
+
|
|
1820
|
+
data.sort(function (a, b) {
|
|
1821
|
+
var d = a.num - b.num;
|
|
1822
|
+
if (!d && by) d = a.by < b.by ? - 1 : a.by > b.by ? 1 : 0;
|
|
1823
|
+
if (!d) d = a.id < b.id ? - 1 : a.id > b.id ? 1 : 0;
|
|
1824
|
+
return desc ? - d : d;
|
|
1825
|
+
});
|
|
1826
|
+
|
|
1827
|
+
// Limit.
|
|
1828
|
+
|
|
1829
|
+
if (parseInt(offset, 10) < 0)
|
|
1830
|
+
offset = '0'; // SORT treats negative offset limit differently from other redis commands.
|
|
1831
|
+
|
|
1832
|
+
if (limit) data = slice(data, offset, count, true);
|
|
1833
|
+
|
|
1834
|
+
// Format.
|
|
1835
|
+
|
|
1836
|
+
var out = [], i;
|
|
1837
|
+
n = data.length;
|
|
1838
|
+
for (i = 0; i < n; i++) {
|
|
1839
|
+
if (get) out.push.apply(out, data[i].get);
|
|
1840
|
+
else out[i] = data[i].id;
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
// Store or return.
|
|
1844
|
+
|
|
1845
|
+
if (store) {
|
|
1846
|
+
this.lStore(store, out);
|
|
1847
|
+
return this.LLEN(store);
|
|
1848
|
+
}
|
|
1849
|
+
else
|
|
1850
|
+
return out;
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
|
|
1854
|
+
// Pubsub.
|
|
1855
|
+
|
|
1856
|
+
, PUBLISH: function (channel, message) {
|
|
1857
|
+
return this.pub(channel, message);
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
|
|
1861
|
+
// Connection.
|
|
1862
|
+
// Quit and select could be implemented on the connection object.
|
|
1863
|
+
|
|
1864
|
+
, PING: function () {
|
|
1865
|
+
if (arguments.length)
|
|
1866
|
+
return BAD_ARGS;
|
|
1867
|
+
|
|
1868
|
+
return PONG;
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
, ECHO: function (message) {
|
|
1872
|
+
return message;
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
|
|
1876
|
+
// Server.
|
|
1877
|
+
// FLUSHALL can be implemented on the connection object.
|
|
1878
|
+
|
|
1879
|
+
, DBSIZE: function () {
|
|
1880
|
+
return this.getKeys().length;
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
, FLUSHDB: function () {
|
|
1884
|
+
var keys = this.getKeys(), i, n = keys.length;
|
|
1885
|
+
for (i = 0; i < n; i++)
|
|
1886
|
+
this.setKey(keys[i], null);
|
|
1887
|
+
|
|
1888
|
+
return OK;
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
, TIME: function () {
|
|
1892
|
+
var time = Date.now()
|
|
1893
|
+
, sec = Math.round(time / 1000)
|
|
1894
|
+
, msec = (time % 1000)* 1000 + Math.floor(Math.random()* 1000);
|
|
1895
|
+
|
|
1896
|
+
return [sec, msec];
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
|
|
1900
|
+
// Helper commands.
|
|
1901
|
+
|
|
1902
|
+
, FAKE_DUMP: function (pattern) {
|
|
1903
|
+
var keys = this.KEYS(pattern), i, n = keys.length, out = [], key, type;
|
|
1904
|
+
|
|
1905
|
+
for (i = 0; i < n; i++) {
|
|
1906
|
+
key = keys[i];
|
|
1907
|
+
type = this.TYPE(key);
|
|
1908
|
+
out.push(key, this.TTL(key), type.getStatus());
|
|
1909
|
+
|
|
1910
|
+
if (type === STRING)
|
|
1911
|
+
out.push(this.GET(key));
|
|
1912
|
+
else if (type === LIST)
|
|
1913
|
+
out.push(this.LRANGE(key, '0', '-1'));
|
|
1914
|
+
else if (type === HASH)
|
|
1915
|
+
out.push(this.HGETALL(key));
|
|
1916
|
+
else if (type === SET)
|
|
1917
|
+
out.push(this.SMEMBERS(key));
|
|
1918
|
+
else if (type === ZSET)
|
|
1919
|
+
out.push(this.ZRANGE(key, '0', '-1', 'withscores'));
|
|
1920
|
+
else
|
|
1921
|
+
throw new Error("WOOT! Key type is " + type);
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
return out;
|
|
1925
|
+
}
|
|
1926
|
+
};
|
|
1927
|
+
|
|
1928
|
+
|
|
1929
|
+
// These don't have an effect on the dataset, so dummies are safe for tests.
|
|
1930
|
+
|
|
1931
|
+
exports.Backend.prototype.AUTH =
|
|
1932
|
+
exports.Backend.prototype.BGREWRITEAOF =
|
|
1933
|
+
exports.Backend.prototype.SAVE =
|
|
1934
|
+
exports.Backend.prototype.BGSAVE = function () { return OK; };
|
|
1935
|
+
|
|
1936
|
+
|
|
1937
|
+
// All of these are implemented at the connection level.
|
|
1938
|
+
|
|
1939
|
+
exports.Backend.prototype.QUIT =
|
|
1940
|
+
|
|
1941
|
+
exports.Backend.prototype.SUBSCRIBE =
|
|
1942
|
+
exports.Backend.prototype.PSUBSCRIBE =
|
|
1943
|
+
exports.Backend.prototype.UNSUBSCRIBE =
|
|
1944
|
+
exports.Backend.prototype.PUNSUBSCRIBE =
|
|
1945
|
+
|
|
1946
|
+
exports.Backend.prototype.MULTI =
|
|
1947
|
+
exports.Backend.prototype.EXEC =
|
|
1948
|
+
exports.Backend.prototype.WATCH =
|
|
1949
|
+
exports.Backend.prototype.UNWATCH =
|
|
1950
|
+
exports.Backend.prototype.SELECT =
|
|
1951
|
+
exports.Backend.prototype.DISCARD = function () { throw new Error("WOOT! This command shouldn't have reached the backend."); };
|