diff --git a/README.md b/README.md index 5e04e777e4..4e7def98f8 100644 --- a/README.md +++ b/README.md @@ -214,6 +214,7 @@ limits total amount of connection tries. Setting this to 1 will prevent any reco * `rename_commands`: *null*; pass a object with renamed commands to use those instead of the original functions. See the [redis security topics](http://redis.io/topics/security) for more info. * `tls`: an object containing options to pass to [tls.connect](http://nodejs.org/api/tls.html#tls_tls_connect_port_host_options_callback), to set up a TLS connection to Redis (if, for example, it is set up to be accessible via a tunnel). +* `prefix`: *null*; pass a string to prefix all used keys with that string as prefix e.g. 'namespace:test' ```js var redis = require("redis"), diff --git a/changelog.md b/changelog.md index 47de9acaa7..faeaec4866 100644 --- a/changelog.md +++ b/changelog.md @@ -5,10 +5,11 @@ Changelog Features -- Added `tls` option to iniate a connection to a redis server behind a TLS proxy. Thanks ([@paddybyers](https://github.com/paddybyers) -- Added a *url* option to pass the connection url with the options object ([@BridgeAR](https://github.com/BridgeAR) -- Added `client.duplicate([options])` to duplicate the current client and return a new one with the same options ([@BridgeAR](https://github.com/BridgeAR) -- Improve performance by up to 20% on almost all use cases ([@BridgeAR](https://github.com/BridgeAR) +- Added `tls` option to iniate a connection to a redis server behind a TLS proxy. Thanks ([@paddybyers](https://github.com/paddybyers)) +- Added `prefix` option to auto key prefix any command with the provided prefix (([@luin](https://github.com/luin) & [@BridgeAR](https://github.com/BridgeAR))) +- Added `url` option to pass the connection url with the options object ([@BridgeAR](https://github.com/BridgeAR)) +- Added `client.duplicate([options])` to duplicate the current client and return a new one with the same options ([@BridgeAR](https://github.com/BridgeAR)) +- Improve performance by up to 20% on almost all use cases ([@BridgeAR](https://github.com/BridgeAR)) Bugfixes diff --git a/generate_commands.js b/generate_commands.js deleted file mode 100644 index a9862321f6..0000000000 --- a/generate_commands.js +++ /dev/null @@ -1,41 +0,0 @@ -'use strict'; - -var http = require("http"), - fs = require("fs"); - -function prettyCurrentTime() { - var date = new Date(); - return date.toLocaleString(); -} - -function write_file(commands, path) { - var file_contents, out_commands; - - console.log("Writing " + Object.keys(commands).length + " commands to " + path); - - file_contents = "'use strict';\n\n// This file was generated by ./generate_commands.js on " + prettyCurrentTime() + "\n"; - - out_commands = Object.keys(commands).map(function (key) { - return key.toLowerCase(); - }); - - file_contents += "module.exports = " + JSON.stringify(out_commands, null, " ") + ";\n"; - - fs.writeFile(path, file_contents); -} - -http.get({host: "redis.io", path: "/commands.json"}, function (res) { - var body = ""; - - console.log("Response from redis.io/commands.json: " + res.statusCode); - - res.on('data', function (chunk) { - body += chunk; - }); - - res.on('end', function () { - write_file(JSON.parse(body), "lib/commands.js"); - }); -}).on('error', function (e) { - console.log("Error fetching command list from redis.io: " + e.message); -}); diff --git a/index.js b/index.js index 9071e72221..c06fc9ddaa 100644 --- a/index.js +++ b/index.js @@ -9,9 +9,7 @@ var Queue = require('double-ended-queue'); var Command = require('./lib/command'); var events = require('events'); var parsers = []; -// This static list of commands is updated from time to time. -// ./lib/commands.js can be updated with generate_commands.js -var commands = require('./lib/commands'); +var commands = require('redis-commands'); var connection_id = 0; var default_port = 6379; var default_host = '127.0.0.1'; @@ -22,7 +20,7 @@ function debug (msg) { if (exports.debug_mode) { console.error(msg); } } exports.debug_mode = /\bredis\b/i.test(process.env.NODE_DEBUG); -// hiredis might not be installed +// Hiredis might not be installed try { parsers.push(require('./lib/parsers/hiredis')); } catch (err) { @@ -189,7 +187,7 @@ RedisClient.prototype.unref = function () { } }; -// flush provided queues, erroring any items with a callback first +// Flush provided queues, erroring any items with a callback first RedisClient.prototype.flush_and_error = function (error, queue_names) { var command_obj; queue_names = queue_names || ['offline_queue', 'command_queue']; @@ -457,7 +455,7 @@ RedisClient.prototype.on_info_cmd = function (err, res) { key = 'db' + i; } - // expose info key/vals to users + // Expose info key/vals to users this.server_info = obj; if (!obj.loading || obj.loading === '0') { @@ -684,7 +682,7 @@ RedisClient.prototype.return_reply = function (reply) { } else { this.pub_sub_mode = true; } - // subscribe commands take an optional callback and also emit an event, but only the first response is included in the callback + // Subscribe commands take an optional callback and also emit an event, but only the first response is included in the callback // TODO - document this or fix it so it works in a more obvious way if (command_obj && typeof command_obj.callback === 'function') { command_obj.callback(null, reply[1]); @@ -723,6 +721,7 @@ RedisClient.prototype.send_command = function (command, args, callback) { command_str = '', buffer_args = false, big_data = false, + prefix_keys, buffer = this.options.return_buffers; if (args === undefined) { @@ -810,7 +809,14 @@ RedisClient.prototype.send_command = function (command, args, callback) { if (typeof this.options.rename_commands !== 'undefined' && this.options.rename_commands[command]) { command = this.options.rename_commands[command]; } - + if (this.options.prefix) { + prefix_keys = commands.getKeyIndexes(command, args); + i = prefix_keys.pop(); + while (i !== undefined) { + args[i] = this.options.prefix + args[i]; + i = prefix_keys.pop(); + } + } // Always use 'Multi bulk commands', but if passed any Buffer args, then do multiple writes, one for each arg. // This means that using Buffers in commands is going to be slower, so use Strings if you don't already have a Buffer. command_str = '*' + (args.length + 1) + '\r\n$' + command.length + '\r\n' + command + '\r\n'; @@ -943,23 +949,7 @@ function Multi(client, args) { } } -RedisClient.prototype.multi = RedisClient.prototype.MULTI = function (args) { - var multi = new Multi(this, args); - multi.exec = multi.EXEC = multi.exec_transaction; - return multi; -}; - -RedisClient.prototype.batch = RedisClient.prototype.BATCH = function (args) { - return new Multi(this, args); -}; - -commands.forEach(function (fullCommand) { - var command = fullCommand.split(' ')[0]; - - // Skip all full commands that have already been added instead of overwriting them over and over again - if (RedisClient.prototype[command]) { - return; - } +commands.list.forEach(function (command) { RedisClient.prototype[command.toUpperCase()] = RedisClient.prototype[command] = function (key, arg, callback) { if (Array.isArray(key)) { @@ -1006,7 +996,17 @@ commands.forEach(function (fullCommand) { }; }); -// store db in this.select_db to restore it on reconnect +RedisClient.prototype.multi = RedisClient.prototype.MULTI = function (args) { + var multi = new Multi(this, args); + multi.exec = multi.EXEC = multi.exec_transaction; + return multi; +}; + +RedisClient.prototype.batch = RedisClient.prototype.BATCH = function (args) { + return new Multi(this, args); +}; + +// Store db in this.select_db to restore it on reconnect RedisClient.prototype.select = RedisClient.prototype.SELECT = function (db, callback) { var self = this; return this.send_command('select', [db], function (err, res) { @@ -1138,7 +1138,7 @@ Multi.prototype.exec_transaction = function (callback) { this._client.cork(len + 2); this.wants_buffers = new Array(len); this.send_command('multi', []); - // drain queue, callback will catch 'QUEUED' or error + // Drain queue, callback will catch 'QUEUED' or error for (var index = 0; index < len; index++) { var args = this.queue.get(index).slice(0); var command = args.shift(); diff --git a/lib/commands.js b/lib/commands.js deleted file mode 100644 index 8082841dc8..0000000000 --- a/lib/commands.js +++ /dev/null @@ -1,197 +0,0 @@ -'use strict'; - -// This file was generated by ./generate_commands.js on Thu Sep 03 2015 02:40:54 GMT+0200 (CEST) -module.exports = [ - "append", - "auth", - "bgrewriteaof", - "bgsave", - "bitcount", - "bitop", - "bitpos", - "blpop", - "brpop", - "brpoplpush", - "client kill", - "client list", - "client getname", - "client pause", - "client setname", - "cluster addslots", - "cluster count-failure-reports", - "cluster countkeysinslot", - "cluster delslots", - "cluster failover", - "cluster forget", - "cluster getkeysinslot", - "cluster info", - "cluster keyslot", - "cluster meet", - "cluster nodes", - "cluster replicate", - "cluster reset", - "cluster saveconfig", - "cluster set-config-epoch", - "cluster setslot", - "cluster slaves", - "cluster slots", - "command", - "command count", - "command getkeys", - "command info", - "config get", - "config rewrite", - "config set", - "config resetstat", - "dbsize", - "debug object", - "debug segfault", - "decr", - "decrby", - "del", - "discard", - "dump", - "echo", - "eval", - "evalsha", - "exec", - "exists", - "expire", - "expireat", - "flushall", - "flushdb", - "geoadd", - "geohash", - "geopos", - "geodist", - "georadius", - "georadiusbymember", - "get", - "getbit", - "getrange", - "getset", - "hdel", - "hexists", - "hget", - "hgetall", - "hincrby", - "hincrbyfloat", - "hkeys", - "hlen", - "hmget", - "hmset", - "hset", - "hsetnx", - "hstrlen", - "hvals", - "incr", - "incrby", - "incrbyfloat", - "info", - "keys", - "lastsave", - "lindex", - "linsert", - "llen", - "lpop", - "lpush", - "lpushx", - "lrange", - "lrem", - "lset", - "ltrim", - "mget", - "migrate", - "monitor", - "move", - "mset", - "msetnx", - "multi", - "object", - "persist", - "pexpire", - "pexpireat", - "pfadd", - "pfcount", - "pfmerge", - "ping", - "psetex", - "psubscribe", - "pubsub", - "pttl", - "publish", - "punsubscribe", - "quit", - "randomkey", - "rename", - "renamenx", - "restore", - "role", - "rpop", - "rpoplpush", - "rpush", - "rpushx", - "sadd", - "save", - "scard", - "script exists", - "script flush", - "script kill", - "script load", - "sdiff", - "sdiffstore", - "select", - "set", - "setbit", - "setex", - "setnx", - "setrange", - "shutdown", - "sinter", - "sinterstore", - "sismember", - "slaveof", - "slowlog", - "smembers", - "smove", - "sort", - "spop", - "srandmember", - "srem", - "strlen", - "subscribe", - "sunion", - "sunionstore", - "sync", - "time", - "ttl", - "type", - "unsubscribe", - "unwatch", - "wait", - "watch", - "zadd", - "zcard", - "zcount", - "zincrby", - "zinterstore", - "zlexcount", - "zrange", - "zrangebylex", - "zrevrangebylex", - "zrangebyscore", - "zrank", - "zrem", - "zremrangebylex", - "zremrangebyrank", - "zremrangebyscore", - "zrevrange", - "zrevrangebyscore", - "zrevrank", - "zscore", - "zunionstore", - "scan", - "sscan", - "hscan", - "zscan" -]; diff --git a/package.json b/package.json index 91ba58dd7b..97aed997ed 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "posttest": "jshint ." }, "dependencies": { - "double-ended-queue": "^2.1.0-0" + "double-ended-queue": "^2.1.0-0", + "redis-commands": "^1.0.1" }, "engines": { "node": ">=0.10.0" diff --git a/test/prefix.spec.js b/test/prefix.spec.js new file mode 100644 index 0000000000..4c909f1af9 --- /dev/null +++ b/test/prefix.spec.js @@ -0,0 +1,119 @@ +'use strict'; + +var assert = require("assert"); +var config = require("./lib/config"); +var helper = require('./helper'); +var redis = config.redis; + +describe("prefix key names", function () { + + helper.allTests(function(parser, ip, args) { + + describe("using " + parser + " and " + ip, function () { + var client = null; + + beforeEach(function(done) { + client = redis.createClient({ + parser: parser, + prefix: 'test:prefix:' + }); + client.on('ready', function () { + client.flushdb(function (err) { + done(err); + }); + }); + }); + + afterEach(function () { + client.end(true); + }); + + it("auto prefix set / get", function (done) { + client.set('key', 'value', function(err, reply) { + assert.strictEqual(reply, 'OK'); + }); + client.get('key', function(err, reply) { + assert.strictEqual(reply, 'value'); + }); + client.getrange('key', 1, -1, function(err, reply) { + assert.strictEqual(reply, 'alue'); + assert.strictEqual(err, null); + }); + client.exists('key', function (err, res) { + assert.strictEqual(res, 1); + }); + client.exists('test:prefix:key', function (err, res) { + // The key will be prefixed itself + assert.strictEqual(res, 0); + }); + client.mset('key2', 'value2', 'key3', 'value3'); + client.keys('*', function (err, res) { + assert.strictEqual(res.length, 3); + assert(res.indexOf('test:prefix:key') !== -1); + assert(res.indexOf('test:prefix:key2') !== -1); + assert(res.indexOf('test:prefix:key3') !== -1); + done(); + }); + }); + + it("auto prefix set / get with .batch", function (done) { + var batch = client.batch(); + batch.set('key', 'value', function(err, reply) { + assert.strictEqual(reply, 'OK'); + }); + batch.get('key', function(err, reply) { + assert.strictEqual(reply, 'value'); + }); + batch.getrange('key', 1, -1, function(err, reply) { + assert.strictEqual(reply, 'alue'); + assert.strictEqual(err, null); + }); + batch.exists('key', function (err, res) { + assert.strictEqual(res, 1); + }); + batch.exists('test:prefix:key', function (err, res) { + // The key will be prefixed itself + assert.strictEqual(res, 0); + }); + batch.mset('key2', 'value2', 'key3', 'value3'); + batch.keys('*', function (err, res) { + assert.strictEqual(res.length, 3); + assert(res.indexOf('test:prefix:key') !== -1); + assert(res.indexOf('test:prefix:key2') !== -1); + assert(res.indexOf('test:prefix:key3') !== -1); + }); + batch.exec(done); + }); + + it("auto prefix set / get with .multi", function (done) { + var multi = client.multi(); + multi.set('key', 'value', function(err, reply) { + assert.strictEqual(reply, 'OK'); + }); + multi.get('key', function(err, reply) { + assert.strictEqual(reply, 'value'); + }); + multi.getrange('key', 1, -1, function(err, reply) { + assert.strictEqual(reply, 'alue'); + assert.strictEqual(err, null); + }); + multi.exists('key', function (err, res) { + assert.strictEqual(res, 1); + }); + multi.exists('test:prefix:key', function (err, res) { + // The key will be prefixed itself + assert.strictEqual(res, 0); + }); + multi.mset('key2', 'value2', 'key3', 'value3'); + multi.keys('*', function (err, res) { + assert.strictEqual(res.length, 3); + assert(res.indexOf('test:prefix:key') !== -1); + assert(res.indexOf('test:prefix:key2') !== -1); + assert(res.indexOf('test:prefix:key3') !== -1); + }); + multi.exec(done); + }); + + }); + }); +});