From a9395c1cf01142a77bfbc9fefdf081a8dc4db7d1 Mon Sep 17 00:00:00 2001 From: UbayGD Date: Wed, 13 Oct 2021 17:29:35 +0100 Subject: [PATCH] Add unlink command support --- lib/client/redis-client.js | 58 ++++++++------- lib/server/keys.js | 56 ++++++++++---- test/client/redis-mock.keys.test.js | 109 +++++++++++++++++++--------- 3 files changed, 144 insertions(+), 79 deletions(-) diff --git a/lib/client/redis-client.js b/lib/client/redis-client.js index a6952f0..6e3d88f 100644 --- a/lib/client/redis-client.js +++ b/lib/client/redis-client.js @@ -9,7 +9,7 @@ const events = require("events"), /** * @deprecated use {@link parsers} instead */ -const parseArguments = function(args, options) { // eslint-disable-line complexity +const parseArguments = function (args, options) { // eslint-disable-line complexity var arr, len = args.length, callback, @@ -39,9 +39,9 @@ const parseArguments = function(args, options) { // eslint-disable-line complexi // arg1 = {k1: v1, k2: v2,} // arg2 = callback arr = [args[0]]; - if(options && options.valueIsString) { + if (options && options.valueIsString) { arr.push(String(args[1])); - } else if(options && options.valueIsBuffer) { + } else if (options && options.valueIsBuffer) { arr.push(args[1]); } else { for (var field in args[1]) { @@ -152,7 +152,7 @@ class RedisClient extends events.EventEmitter { // Emit the message to ALL matching subscriptions Object.keys(this.psubscriptions).forEach((key) => { - if(this.psubscriptions[key].test(ch)) { + if (this.psubscriptions[key].test(ch)) { this.emit('pmessage', key, ch, msg); return true; } @@ -215,7 +215,7 @@ RedisClient.prototype.publish = function (channel, msg, callback) { /** * multi */ -RedisClient.prototype.multi = RedisClient.prototype.batch = function(commands) { +RedisClient.prototype.multi = RedisClient.prototype.batch = function (commands) { return multi.multi(this, commands, false); }; RedisClient.prototype.batch = function (commands) { @@ -228,18 +228,22 @@ RedisClient.prototype.batch = function (commands) { const getKeysVarArgs = function (args) { var keys = []; - var hasCallback = typeof(args[args.length - 1]) === 'function'; + var hasCallback = typeof (args[args.length - 1]) === 'function'; for (var i = 0; i < (hasCallback ? args.length - 1 : args.length); i++) { keys.push(args[i]); } var callback = hasCallback ? args[args.length - 1] : undefined; - return {keys: keys, callback: callback}; + return { keys: keys, callback: callback }; }; RedisClient.prototype.del = RedisClient.prototype.DEL = function (keys, callback) { this._selectedDb.del(keys, callback); }; +RedisClient.prototype.unlink = RedisClient.prototype.UNLINK = function (keys, callback) { + this._selectedDb.unlink(keys, callback); +}; + RedisClient.prototype.exists = RedisClient.prototype.EXISTS = function (keys, callback) { const args = getKeysVarArgs(arguments); keys = args.keys; @@ -247,7 +251,7 @@ RedisClient.prototype.exists = RedisClient.prototype.EXISTS = function (keys, ca this._selectedDb.exists(keys, callback); }; -RedisClient.prototype.type = RedisClient.prototype.TYPE = function(key, callback) { +RedisClient.prototype.type = RedisClient.prototype.TYPE = function (key, callback) { this._selectedDb.type(key, callback); }; @@ -351,7 +355,7 @@ RedisClient.prototype.set = RedisClient.prototype.SET = function (...userArgs) { ); }; -RedisClient.prototype.append = RedisClient.prototype.APPEND = function(...userArgs) { +RedisClient.prototype.append = RedisClient.prototype.APPEND = function (...userArgs) { exec(parsers.append, userArgs, (args, cb) => this._selectedDb.append(args.default.key, args.default.value, cb) ); @@ -444,12 +448,12 @@ RedisClient.prototype.hscan = RedisClient.prototype.HSCAN = function () { let match = '*'; let count = 10; - if(args.length > 0) { + if (args.length > 0) { for (let i = 0; i < args.length; i++) { - if(typeof args[i] === 'string' && args[i].toLowerCase() === "match") { - match = args[i+1]; - } else if(typeof args[i] === 'string' && args[i].toLowerCase() === "count") { - count = args[i+1]; + if (typeof args[i] === 'string' && args[i].toLowerCase() === "match") { + match = args[i + 1]; + } else if (typeof args[i] === 'string' && args[i].toLowerCase() === "count") { + count = args[i + 1]; } } } @@ -497,7 +501,7 @@ RedisClient.prototype.rpoplpush = RedisClient.prototype.RPOPLPUSH = function (so RedisClient.prototype._bpop = function (fn, key, timeout, callback) { const keys = []; - const hasCallback = typeof(arguments[arguments.length - 1]) === "function"; + const hasCallback = typeof (arguments[arguments.length - 1]) === "function"; for (let i = 1; i < (hasCallback ? arguments.length - 2 : arguments.length - 1); i++) { keys.push(arguments[i]); } @@ -564,7 +568,7 @@ RedisClient.prototype.srandmember = RedisClient.prototype.SRANDMEMBER = function this._selectedDb.srandmember(key, count, callback); }; -RedisClient.prototype.sscan = RedisClient.prototype.SSCAN = function(...userArgs) { +RedisClient.prototype.sscan = RedisClient.prototype.SSCAN = function (...userArgs) { exec(parsers.sscan, userArgs, (args, cb) => { this._selectedDb.sscan(args.default.key, args.default.cursor, args.named.match, args.named.count, cb); }); @@ -652,12 +656,12 @@ RedisClient.prototype.zrevrank = RedisClient.prototype.ZREVRANK = function () { this._selectedDb.zrevrank(...args); }; -RedisClient.prototype.zunionstore = RedisClient.prototype.ZUNIONSTORE = function() { +RedisClient.prototype.zunionstore = RedisClient.prototype.ZUNIONSTORE = function () { const args = parseArguments(arguments); this._selectedDb.zunionstore(...args); }; -RedisClient.prototype.zinterstore = RedisClient.prototype.ZINTERSTORE = function() { +RedisClient.prototype.zinterstore = RedisClient.prototype.ZINTERSTORE = function () { const args = parseArguments(arguments); this._selectedDb.zinterstore(...args); }; @@ -672,7 +676,7 @@ RedisClient.prototype.zscore = RedisClient.prototype.ZSCORE = function () { */ RedisClient.prototype.send_command = RedisClient.prototype.SEND_COMMAND = function (callback) { - if (typeof(arguments[arguments.length - 1]) === 'function') { + if (typeof (arguments[arguments.length - 1]) === 'function') { arguments[arguments.length - 1](); } }; @@ -714,13 +718,13 @@ RedisClient.prototype.script = RedisClient.prototype.SCRIPT = function (...userA * Mirror all commands for Multi */ types.getMethods(RedisClient).public() -.skip('duplicate', 'quit', 'end') -.forEach((methodName) => { - multi.Multi.prototype[methodName] = function (...args) { - this._command(methodName, args); - //Return this for chaining - return this; - }; -}); + .skip('duplicate', 'quit', 'end') + .forEach((methodName) => { + multi.Multi.prototype[methodName] = function (...args) { + this._command(methodName, args); + //Return this for chaining + return this; + }; + }); module.exports = RedisClient; diff --git a/lib/server/keys.js b/lib/server/keys.js index 470ade3..63682df 100644 --- a/lib/server/keys.js +++ b/lib/server/keys.js @@ -25,25 +25,49 @@ exports.del = function (keys, callback) { helpers.callCallback(callback, null, keysDeleted); }; +/** + * Unlink + */ +exports.unlink = function (keys, callback) { + + if (!(keys instanceof Array)) { + keys = [keys]; + } + + let keysUnlinked = 0; + + for (let i = 0; i < keys.length; i++) { + + if (keys[i] in this.storage) { + + delete this.storage[keys[i]]; + keysUnlinked++; + + } + } + + helpers.callCallback(callback, null, keysUnlinked); +}; + /** * Exists */ exports.exists = function (keys, callback) { if (!(keys instanceof Array)) { - keys = [keys]; + keys = [keys]; } let result = 0; for (let i = 0; i < keys.length; i++) { - if( keys[i] in this.storage) { - result++; - } + if (keys[i] in this.storage) { + result++; + } } helpers.callCallback(callback, null, result); }; -exports.type = function(key, callback) { +exports.type = function (key, callback) { const type = key in this.storage ? this.storage[key].type : "none"; @@ -61,7 +85,7 @@ exports.expire = function (key, seconds, callback) { if (obj) { var now = new Date().getTime(); - var milli = Math.min(seconds*1000, Math.pow(2, 31) - 1); + var milli = Math.min(seconds * 1000, Math.pow(2, 31) - 1); if (this.storage[key]._expire) { clearTimeout(this.storage[key]._expire); @@ -69,7 +93,7 @@ exports.expire = function (key, seconds, callback) { this.storage[key].expires = new Date(now + milli); var _expire = setTimeout(() => { - delete this.storage[key]; + delete this.storage[key]; }, milli); if (_expire.unref) { _expire.unref(); @@ -83,7 +107,7 @@ exports.expire = function (key, seconds, callback) { }; exports.pexpire = function (key, ms, callback) { - const computedSeconds = ms > 0 ? ms/1000 : ms; + const computedSeconds = ms > 0 ? ms / 1000 : ms; return this.expire(key, computedSeconds, (err, seconds) => { helpers.callCallback(callback, err, seconds); }); @@ -121,7 +145,7 @@ exports.expireat = function (key, timestamp, callback) { }; exports.pexpireat = function (key, timestamp, callback) { - return this.expireat(key, timestamp / 1000, (err, result) => { + return this.expireat(key, timestamp / 1000, (err, result) => { helpers.callCallback(callback, err, result); }); }; @@ -206,9 +230,9 @@ exports.scan = function (index, pattern, count, callback) { if (idx >= index && regex.test(key)) { keys.push(key); count--; - if(count === 0) { - resIdx = idx+1; - break; + if (count === 0) { + resIdx = idx + 1; + break; } } idx++; @@ -225,10 +249,10 @@ exports.rename = function (key, newKey, callback) { var err = null; if (key in this.storage) { - this.storage[newKey] = this.storage[key]; - delete this.storage[key]; + this.storage[newKey] = this.storage[key]; + delete this.storage[key]; } else { - err = new Error("ERR no such key"); + err = new Error("ERR no such key"); } helpers.callCallback(callback, err, "OK"); @@ -262,7 +286,7 @@ exports.renamenx = function (key, newKey, callback) { * Dbsize * http://redis.io/commands/dbsize */ -exports.dbsize = function(callback) { +exports.dbsize = function (callback) { var size = Object.keys(this.storage).length || 0; helpers.callCallback(callback, null, size); }; diff --git a/test/client/redis-mock.keys.test.js b/test/client/redis-mock.keys.test.js index 8f73156..254fe62 100644 --- a/test/client/redis-mock.keys.test.js +++ b/test/client/redis-mock.keys.test.js @@ -51,6 +51,43 @@ describe("del", function () { }); +describe("unlink", function () { + + it("should do nothing with non-existant keys", function (done) { + r.unlink(["key1", "key2", "key3"], function (err, result) { + result.should.equal(0); + r.unlink("key4", function (err, result) { + result.should.equal(0); + done(); + }); + }); + }); + + it("should unlink existing keys", function (done) { + r.set("test", "test", function (err, result) { + r.unlink("test", function (err, result) { + result.should.equal(1); + r.get("test", function (err, result) { + should.not.exist(result); + done(); + }); + }); + }); + }); + + it("should unlink multiple keys", function (done) { + r.set("test", "val", function (err, result) { + r.set("test2", "val2", function (err, result) { + r.unlink(["test", "test2", "noexistant"], function (err, result) { + result.should.equal(2); + done(); + }); + }); + }); + }); + +}); + describe("exists", function () { it("should return 0 for non-existing keys", function (done) { @@ -86,26 +123,26 @@ describe("exists", function () { r.set("test", "test", function (err, result) { r.set("test2", "test", function (err, result) { r.exists("test", "test2", "nonexistant", function (err, result) { - result.should.eql(2); - done(); + result.should.eql(2); + done(); }); }); }); }); }); -describe("type", function() { - it('should return "none" for non existent keys', function(done) { - r.type("testKey", function(err, result) { - result.should.equal("none"); +describe("type", function () { + it('should return "none" for non existent keys', function (done) { + r.type("testKey", function (err, result) { + result.should.equal("none"); - done(); + done(); }); }); - it('should return type "string" for key that exists with string value', function(done) { - r.set("testKey", "testValue", function(err, result) { - r.type("testKey", function(err, result) { + it('should return type "string" for key that exists with string value', function (done) { + r.set("testKey", "testValue", function (err, result) { + r.type("testKey", function (err, result) { result.should.equal("string"); done(); @@ -113,9 +150,9 @@ describe("type", function() { }); }); - it('should return type "list" for key that exists with list value', function(done) { + it('should return type "list" for key that exists with list value', function (done) { r.lpush("testValue", 1, function (err, result) { - r.type("testValue", function(err, result) { + r.type("testValue", function (err, result) { result.should.equal("list"); done(); @@ -125,7 +162,7 @@ describe("type", function() { it('should return type "set" for key that exists with set value', function (done) { r.sadd('testKey', 'testValue', function (err, result) { - r.type("testKey", function(err, result) { + r.type("testKey", function (err, result) { result.should.equal("set"); done(); @@ -134,8 +171,8 @@ describe("type", function() { }); it('should return type "zset" for key that exists with zset value', function (done) { - r.zadd(["testKey", 1, 'm1'], function(err, result) { - r.type("testKey", function(err, result) { + r.zadd(["testKey", 1, 'm1'], function (err, result) { + r.type("testKey", function (err, result) { result.should.equal("zset"); done(); @@ -145,7 +182,7 @@ describe("type", function() { it('should return type "hash" for key that exists with hash value', function (done) { r.hset("testHash", "testKey", "test", function (err, result) { - r.type("testHash", function(err, result) { + r.type("testHash", function (err, result) { result.should.equal("hash"); done(); @@ -189,7 +226,7 @@ describe("expire", function () { it("accepts timeouts exceeding 2**31 msec", function (done) { r.set("test_exceeds", "val", function (err, result) { - r.expire("test_exceeds", 86400*31 /* one month */, function (err, result) { + r.expire("test_exceeds", 86400 * 31 /* one month */, function (err, result) { result.should.equal(1); setTimeout(function () { r.exists("test_exceeds", function (err, result) { @@ -204,7 +241,7 @@ describe("expire", function () { }); describe("expireat", function () { - function expireat () { + function expireat() { return Math.floor(new Date().getTime() / 1000); } @@ -554,10 +591,10 @@ describe('scan', function () { let index = 0; let iterations = 0; - const loop = function() { + const loop = function () { iterations++; r.scan(index, 'match', '*', 'count', 2, function (err, indexAndKeys) { - if(err) { + if (err) { done(err); return; } @@ -581,9 +618,9 @@ describe('scan', function () { it("should scan all keys - *", function (done) { var keys = []; var index = 0; - var loop = function() { + var loop = function () { r.scan(index, 'match', '*', 'count', 1000, function (err, indexAndKeys) { - if(err) { + if (err) { done(err); return; } @@ -607,7 +644,7 @@ describe('scan', function () { it("when match specified, should return only the matching values", function (done) { r.scan('0', 'match', 'hel*', 'count', 1000, function (err, indexAndKeys) { - if(err) { + if (err) { done(err); return; } @@ -625,9 +662,9 @@ describe('scan', function () { it("no 'match' parameter specified, should default to '*'", function (done) { var keys = []; var index = 0; - var loop = function() { + var loop = function () { r.scan(index, 'count', 1000, function (err, indexAndKeys) { - if(err) { + if (err) { done(err); return; } @@ -651,7 +688,7 @@ describe('scan', function () { it("no 'count' parameter specified, should return everything", function (done) { r.scan('0', 'match', 'hel*', function (err, indexAndKeys) { - if(err) { + if (err) { done(err); return; } @@ -663,7 +700,7 @@ describe('scan', function () { it("no optional parameter specified, should return everything", function (done) { r.scan('0', function (err, indexAndKeys) { - if(err) { + if (err) { done(err); return; } @@ -679,7 +716,7 @@ describe("rename", function () { it("should return true and set key to newKey when newKey is empty", function (done) { r.set("test", "test", function (err, result) { r.rename("test", "newTest", function (err, result) { - if(err) { + if (err) { done(err); return; } @@ -699,7 +736,7 @@ describe("rename", function () { r.set("newTest", "newTest", function (err, result) { r.set("test", "test", function (err, result) { r.rename("test", "newTest", function (err, result) { - if(err) { + if (err) { done(err); return; } @@ -728,7 +765,7 @@ describe("renamenx", function () { it("should return true and set key to newKey when newKey does not exist", function (done) { r.set("test", "test", function (err, result) { r.renamenx("test", "newTest", function (err, result) { - if(err) { + if (err) { done(err); return; } @@ -748,7 +785,7 @@ describe("renamenx", function () { r.set("newTest", "newTest", function (err, result) { r.set("test", "test", function (err, result) { r.renamenx("test", "newTest", function (err, result) { - if(err) { + if (err) { done(err); return; } @@ -783,15 +820,15 @@ describe("renamenx", function () { }); describe("dbsize", function () { - it("should return 0 for empty storage", function(done) { - r.dbsize(function(err, result) { + it("should return 0 for empty storage", function (done) { + r.dbsize(function (err, result) { result.should.equal(0); done(); }); }); - it("should return number of keys in storage", function(done) { - r.set('test','test', function(err, result) { - r.dbsize(function(err, result) { + it("should return number of keys in storage", function (done) { + r.set('test', 'test', function (err, result) { + r.dbsize(function (err, result) { result.should.equal(1); done(); });