Skip to content

Commit fab9666

Browse files
committed
add namespace support
1 parent be0f326 commit fab9666

File tree

2 files changed

+163
-5
lines changed

2 files changed

+163
-5
lines changed

lib/client.js

Lines changed: 92 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,22 @@ function validateKey(key, operation) {
1919
misc.assert(key.length < 250, 'Key must be less than 250 characters long');
2020
}
2121

22+
function validateNamespaceKey(key, operation) {
23+
validateKey(key, operation);
24+
misc.assert(key.length < 230, 'Key must be less than 230 characters long when using namespaces');
25+
}
26+
27+
function keyWithPrefix(key, prefix) {
28+
if (_.isUndefined(prefix)) {
29+
// only prefix was supplied
30+
// return a curried funtion that expects the key
31+
return function(key) {
32+
return _.toString(prefix) + '_' + key;
33+
};
34+
}
35+
return _.toString(prefix) + '_' + key;
36+
}
37+
2238
/**
2339
* Constructor - Initiate client
2440
*/
@@ -324,14 +340,26 @@ Client.prototype.deleteMulti = function(keys, cb) {
324340
* @returns {Promise}
325341
*/
326342
Client.prototype.set = function(key, val, ttl, cb) {
327-
validateKey(key, 'set');
328-
343+
var command = 'set';
344+
var self = this;
329345
if (typeof ttl === 'function') {
330346
cb = ttl;
331347
ttl = 0;
332348
}
349+
350+
if (_.isPlainObject(ttl) && _.isString(ttl.namespace)) {
351+
var namespace = ttl.namespace;
352+
validateNamespaceKey(key, command);
353+
354+
return this.getNamespacePrefix(namespace).then(function (prefix) {
355+
key = keyWithPrefix(key, prefix);
356+
return self.run(command, [key, val, ttl], cb);
357+
});
358+
}
333359

334-
return this.run('set', [key, val, ttl], cb);
360+
validateKey(key, command);
361+
362+
return this.run(command, [key, val, ttl], cb);
335363
};
336364

337365
/**
@@ -384,16 +412,34 @@ Client.prototype.gets = function(key, opts, cb) {
384412
* @returns {Promise}
385413
*/
386414
Client.prototype.get = function(key, opts, cb) {
415+
var command = 'get';
416+
var self = this;
387417
if (typeof opts === 'function' && typeof cb === 'undefined') {
388418
cb = opts;
389419
opts = {};
390420
}
391421

422+
if (_.isPlainObject(opts) && _.isString(opts.namespace)) {
423+
var namespace = opts.namespace;
424+
validateNamespaceKey(key, command);
425+
426+
return this.getNamespacePrefix(namespace).then(function (prefix) {
427+
428+
if(_.isArray(key)) {
429+
key = _.map(key, keyWithPrefix(prefix));
430+
return this.getMulti(key, opts, cb);
431+
} else {
432+
key = keyWithPrefix(key, prefix);
433+
return self.run(command, [key, opts], cb);
434+
}
435+
});
436+
}
437+
392438
if (_.isArray(key)) {
393439
return this.getMulti(key, opts, cb);
394440
} else {
395-
validateKey(key, 'get');
396-
return this.run('get', [key, opts], cb);
441+
validateKey(key, command);
442+
return this.run(command, [key, opts], cb);
397443
}
398444
};
399445

@@ -584,6 +630,47 @@ Client.prototype.cachedump = function(slabsId, limit, cb) {
584630
return this.run('stats cachedump', [slabsId, limit], cb);
585631
};
586632

633+
/**
634+
* getNamespacePrefix() - Get prefix value for the provided namespace
635+
*
636+
* @param {String} namespace - The namespace for which prefix is to be fetched
637+
* @param {Function} [cb] - The (optional) callback called on completion
638+
* @returns {Promise}
639+
*/
640+
Client.prototype.getNamespacePrefix = function(namespace, cb) {
641+
validateKey(namespace);
642+
var self = this;
643+
var timestamp = _.toNumber(new Date());
644+
var prefix = this.get(namespace);
645+
646+
return prefix.then(function (value) {
647+
if (!value) {
648+
// the namespace is not set
649+
return self.add(namespace, timestamp);
650+
}
651+
return value;
652+
}).then(function (value) {
653+
// if value is undefined it means we just
654+
// added the namespace to cache.
655+
return value || timestamp;
656+
}).nodeify(cb);
657+
};
658+
659+
660+
/**
661+
* invalidateNamespace() - Invalidate all data for a namespace.
662+
* Warning! This does not flush the cache, but instead
663+
* relies on the unused values to expire on their own
664+
*
665+
* @param {String} namespace - The namespace to invalidate
666+
* @param {Function} [cb] - The (optional) callback called on completion
667+
* @returns {Promise}
668+
*/
669+
Client.prototype.invalidateNamespace = function(namespace, cb) {
670+
validateKey(namespace);
671+
return this.incr(namespace, cb);
672+
};
673+
587674
/**
588675
* version() - Get current Memcached version from the server
589676
* @param {Function} [cb] - The (optional) callback called on completion

test/client.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,20 @@ describe('Client', function() {
298298
});
299299
});
300300

301+
describe('with namespace', function () {
302+
it('should work', function () {
303+
var key = getKey(), ns = getKey(), val = chance.word();
304+
305+
return cache.set(key, val, { namespace: ns })
306+
.then(function () {
307+
return cache.get(key, { namespace: ns });
308+
})
309+
.then(function (v) {
310+
val.should.equal(v);
311+
});
312+
});
313+
});
314+
301315
it('does not throw an error when setting a value number', function() {
302316
var key = chance.guid(), val = chance.natural();
303317

@@ -1277,6 +1291,63 @@ describe('Client', function() {
12771291
});
12781292
});
12791293

1294+
1295+
describe('namespace', function () {
1296+
var cache;
1297+
var savedPrefix;
1298+
var namespace = getKey();
1299+
beforeEach(function () {
1300+
cache = new Client();
1301+
});
1302+
1303+
1304+
describe('getNamespacePrefix', function () {
1305+
it('exists', function () {
1306+
return cache.should.have.property('getNamespacePrefix');
1307+
});
1308+
1309+
it('when namespace is not already set', function () {
1310+
return cache.getNamespacePrefix(namespace).then(function(prefix) {
1311+
expect(prefix).to.be.a('number');
1312+
savedPrefix = prefix;
1313+
});
1314+
});
1315+
1316+
it('when namespace is already set', function () {
1317+
return cache.getNamespacePrefix(namespace).then(function(prefix) {
1318+
expect(prefix).to.equal(savedPrefix);
1319+
});
1320+
});
1321+
});
1322+
1323+
describe('invalidateNamespace', function () {
1324+
it('exists', function () {
1325+
return cache.should.have.property('invalidateNamespace');
1326+
});
1327+
1328+
it('should invalidate previously stored keys', function () {
1329+
var key = getKey(), ns = getKey(), val = chance.word();
1330+
1331+
return cache.set(key, val, { namespace: ns })
1332+
.then(function () {
1333+
return cache.get(key, { namespace: ns });
1334+
})
1335+
.then(function (v) {
1336+
val.should.equal(v);
1337+
})
1338+
.then(function() {
1339+
return cache.invalidateNamespace(ns);
1340+
})
1341+
.then(function () {
1342+
return cache.get(key, { namespace: ns });
1343+
})
1344+
.then(function (v) {
1345+
expect(v).to.be.null;
1346+
});
1347+
});
1348+
});
1349+
});
1350+
12801351
describe('version', function () {
12811352
var cache;
12821353
beforeEach(function() {

0 commit comments

Comments
 (0)