From 04bc9f2e74fd53a7c70ca972d0572a12668c08db Mon Sep 17 00:00:00 2001 From: Shouvik Roy Date: Thu, 21 May 2020 12:26:24 +0530 Subject: [PATCH] add namespace support --- lib/client.js | 97 +++++++++++++++++++++++++++++++++++++++++++++++--- test/client.js | 71 ++++++++++++++++++++++++++++++++++++ 2 files changed, 163 insertions(+), 5 deletions(-) diff --git a/lib/client.js b/lib/client.js index 8b970fa..9f90e60 100644 --- a/lib/client.js +++ b/lib/client.js @@ -19,6 +19,22 @@ function validateKey(key, operation) { misc.assert(key.length < 250, 'Key must be less than 250 characters long'); } +function validateNamespaceKey(key, operation) { + validateKey(key, operation); + misc.assert(key.length < 230, 'Key must be less than 230 characters long when using namespaces'); +} + +function keyWithPrefix(key, prefix) { + if (_.isUndefined(prefix)) { + // only prefix was supplied + // return a curried funtion that expects the key + return function(key) { + return _.toString(prefix) + '_' + key; + }; + } + return _.toString(prefix) + '_' + key; +} + /** * Constructor - Initiate client */ @@ -324,14 +340,26 @@ Client.prototype.deleteMulti = function(keys, cb) { * @returns {Promise} */ Client.prototype.set = function(key, val, ttl, cb) { - validateKey(key, 'set'); - + var command = 'set'; + var self = this; if (typeof ttl === 'function') { cb = ttl; ttl = 0; } + + if (_.isPlainObject(ttl) && _.isString(ttl.namespace)) { + var namespace = ttl.namespace; + validateNamespaceKey(key, command); + + return this.getNamespacePrefix(namespace).then(function (prefix) { + key = keyWithPrefix(key, prefix); + return self.run(command, [key, val, ttl], cb); + }); + } - return this.run('set', [key, val, ttl], cb); + validateKey(key, command); + + return this.run(command, [key, val, ttl], cb); }; /** @@ -384,16 +412,34 @@ Client.prototype.gets = function(key, opts, cb) { * @returns {Promise} */ Client.prototype.get = function(key, opts, cb) { + var command = 'get'; + var self = this; if (typeof opts === 'function' && typeof cb === 'undefined') { cb = opts; opts = {}; } + if (_.isPlainObject(opts) && _.isString(opts.namespace)) { + var namespace = opts.namespace; + validateNamespaceKey(key, command); + + return this.getNamespacePrefix(namespace).then(function (prefix) { + + if(_.isArray(key)) { + key = _.map(key, keyWithPrefix(prefix)); + return this.getMulti(key, opts, cb); + } else { + key = keyWithPrefix(key, prefix); + return self.run(command, [key, opts], cb); + } + }); + } + if (_.isArray(key)) { return this.getMulti(key, opts, cb); } else { - validateKey(key, 'get'); - return this.run('get', [key, opts], cb); + validateKey(key, command); + return this.run(command, [key, opts], cb); } }; @@ -584,6 +630,47 @@ Client.prototype.cachedump = function(slabsId, limit, cb) { return this.run('stats cachedump', [slabsId, limit], cb); }; +/** + * getNamespacePrefix() - Get prefix value for the provided namespace + * + * @param {String} namespace - The namespace for which prefix is to be fetched + * @param {Function} [cb] - The (optional) callback called on completion + * @returns {Promise} + */ +Client.prototype.getNamespacePrefix = function(namespace, cb) { + validateKey(namespace); + var self = this; + var timestamp = _.toNumber(new Date()); + var prefix = this.get(namespace); + + return prefix.then(function (value) { + if (!value) { + // the namespace is not set + return self.add(namespace, timestamp); + } + return value; + }).then(function (value) { + // if value is undefined it means we just + // added the namespace to cache. + return value || timestamp; + }).nodeify(cb); +}; + + +/** + * invalidateNamespace() - Invalidate all data for a namespace. + * Warning! This does not flush the cache, but instead + * relies on the unused values to expire on their own + * + * @param {String} namespace - The namespace to invalidate + * @param {Function} [cb] - The (optional) callback called on completion + * @returns {Promise} + */ +Client.prototype.invalidateNamespace = function(namespace, cb) { + validateKey(namespace); + return this.incr(namespace, cb); +}; + /** * version() - Get current Memcached version from the server * @param {Function} [cb] - The (optional) callback called on completion diff --git a/test/client.js b/test/client.js index 1ec2b74..daa6e4d 100644 --- a/test/client.js +++ b/test/client.js @@ -298,6 +298,20 @@ describe('Client', function() { }); }); + describe('with namespace', function () { + it('should work', function () { + var key = getKey(), ns = getKey(), val = chance.word(); + + return cache.set(key, val, { namespace: ns }) + .then(function () { + return cache.get(key, { namespace: ns }); + }) + .then(function (v) { + val.should.equal(v); + }); + }); + }); + it('does not throw an error when setting a value number', function() { var key = chance.guid(), val = chance.natural(); @@ -1277,6 +1291,63 @@ describe('Client', function() { }); }); + + describe('namespace', function () { + var cache; + var savedPrefix; + var namespace = getKey(); + beforeEach(function () { + cache = new Client(); + }); + + + describe('getNamespacePrefix', function () { + it('exists', function () { + return cache.should.have.property('getNamespacePrefix'); + }); + + it('when namespace is not already set', function () { + return cache.getNamespacePrefix(namespace).then(function(prefix) { + expect(prefix).to.be.a('number'); + savedPrefix = prefix; + }); + }); + + it('when namespace is already set', function () { + return cache.getNamespacePrefix(namespace).then(function(prefix) { + expect(prefix).to.equal(savedPrefix); + }); + }); + }); + + describe('invalidateNamespace', function () { + it('exists', function () { + return cache.should.have.property('invalidateNamespace'); + }); + + it('should invalidate previously stored keys', function () { + var key = getKey(), ns = getKey(), val = chance.word(); + + return cache.set(key, val, { namespace: ns }) + .then(function () { + return cache.get(key, { namespace: ns }); + }) + .then(function (v) { + val.should.equal(v); + }) + .then(function() { + return cache.invalidateNamespace(ns); + }) + .then(function () { + return cache.get(key, { namespace: ns }); + }) + .then(function (v) { + expect(v).to.be.null; + }); + }); + }); + }); + describe('version', function () { var cache; beforeEach(function() {