1
0
mirror of https://github.com/redis/node-redis.git synced 2025-08-04 15:02:09 +03:00

Add prefix option

Fixes #323

Add key prefix tests

Add changelog entry for prefix
This commit is contained in:
Ruben Bridgewater
2015-11-23 23:09:57 +01:00
parent a8c3675218
commit f877c3950c
7 changed files with 154 additions and 270 deletions

View File

@@ -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. * `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), * `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). 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 ```js
var redis = require("redis"), var redis = require("redis"),

View File

@@ -5,10 +5,11 @@ Changelog
Features Features
- Added `tls` option to iniate a connection to a redis server behind a TLS proxy. Thanks ([@paddybyers](https://github.com/paddybyers) - 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 `prefix` option to auto key prefix any command with the provided prefix (([@luin](https://github.com/luin) & [@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) - Added `url` option to pass the connection url with the options object ([@BridgeAR](https://github.com/BridgeAR))
- Improve performance by up to 20% on almost all use cases ([@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 Bugfixes

View File

@@ -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);
});

View File

@@ -9,9 +9,7 @@ var Queue = require('double-ended-queue');
var Command = require('./lib/command'); var Command = require('./lib/command');
var events = require('events'); var events = require('events');
var parsers = []; var parsers = [];
// This static list of commands is updated from time to time. var commands = require('redis-commands');
// ./lib/commands.js can be updated with generate_commands.js
var commands = require('./lib/commands');
var connection_id = 0; var connection_id = 0;
var default_port = 6379; var default_port = 6379;
var default_host = '127.0.0.1'; 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); exports.debug_mode = /\bredis\b/i.test(process.env.NODE_DEBUG);
// hiredis might not be installed // Hiredis might not be installed
try { try {
parsers.push(require('./lib/parsers/hiredis')); parsers.push(require('./lib/parsers/hiredis'));
} catch (err) { } 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) { RedisClient.prototype.flush_and_error = function (error, queue_names) {
var command_obj; var command_obj;
queue_names = queue_names || ['offline_queue', 'command_queue']; queue_names = queue_names || ['offline_queue', 'command_queue'];
@@ -457,7 +455,7 @@ RedisClient.prototype.on_info_cmd = function (err, res) {
key = 'db' + i; key = 'db' + i;
} }
// expose info key/vals to users // Expose info key/vals to users
this.server_info = obj; this.server_info = obj;
if (!obj.loading || obj.loading === '0') { if (!obj.loading || obj.loading === '0') {
@@ -684,7 +682,7 @@ RedisClient.prototype.return_reply = function (reply) {
} else { } else {
this.pub_sub_mode = true; 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 // TODO - document this or fix it so it works in a more obvious way
if (command_obj && typeof command_obj.callback === 'function') { if (command_obj && typeof command_obj.callback === 'function') {
command_obj.callback(null, reply[1]); command_obj.callback(null, reply[1]);
@@ -723,6 +721,7 @@ RedisClient.prototype.send_command = function (command, args, callback) {
command_str = '', command_str = '',
buffer_args = false, buffer_args = false,
big_data = false, big_data = false,
prefix_keys,
buffer = this.options.return_buffers; buffer = this.options.return_buffers;
if (args === undefined) { 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]) { if (typeof this.options.rename_commands !== 'undefined' && this.options.rename_commands[command]) {
command = 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. // 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. // 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'; 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) { commands.list.forEach(function (command) {
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;
}
RedisClient.prototype[command.toUpperCase()] = RedisClient.prototype[command] = function (key, arg, callback) { RedisClient.prototype[command.toUpperCase()] = RedisClient.prototype[command] = function (key, arg, callback) {
if (Array.isArray(key)) { 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) { RedisClient.prototype.select = RedisClient.prototype.SELECT = function (db, callback) {
var self = this; var self = this;
return this.send_command('select', [db], function (err, res) { 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._client.cork(len + 2);
this.wants_buffers = new Array(len); this.wants_buffers = new Array(len);
this.send_command('multi', []); 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++) { for (var index = 0; index < len; index++) {
var args = this.queue.get(index).slice(0); var args = this.queue.get(index).slice(0);
var command = args.shift(); var command = args.shift();

View File

@@ -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"
];

View File

@@ -20,7 +20,8 @@
"posttest": "jshint ." "posttest": "jshint ."
}, },
"dependencies": { "dependencies": {
"double-ended-queue": "^2.1.0-0" "double-ended-queue": "^2.1.0-0",
"redis-commands": "^1.0.1"
}, },
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"

119
test/prefix.spec.js Normal file
View File

@@ -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);
});
});
});
});