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

Add support for camelCase

Fixes missing `EXEC_BATCH` on multi
This commit is contained in:
Ruben Bridgewater
2016-04-13 03:54:19 +02:00
parent dfd493f6ee
commit d2b8f2f391
11 changed files with 154 additions and 34 deletions

View File

@@ -49,6 +49,8 @@ This will display:
mjr:~/work/node_redis (master)$
Note that the API is entirely asynchronous. To get data back from the server, you'll need to use a callback.
From v.2.6 on the API supports camelCase and snack_case and all options / variables / events etc. can be used either way.
It is recommended to use camelCase as this is the default for the Node.js landscape.
### Promises
@@ -109,8 +111,6 @@ client.get("missingkey", function(err, reply) {
For a list of Redis commands, see [Redis Command Reference](http://redis.io/commands)
The commands can be specified in uppercase or lowercase for convenience. `client.get()` is the same as `client.GET()`.
Minimal parsing is done on the replies. Commands that return a integer return JavaScript Numbers, arrays return JavaScript Array. `HGETALL` returns an Object keyed by the hash keys. All strings will either be returned as string or as buffer depending on your setting.
Please be aware that sending null, undefined and Boolean values will result in the value coerced to a string!

View File

@@ -479,13 +479,19 @@ RedisClient.prototype.send_offline_queue = function () {
var retry_connection = function (self, error) {
debug('Retrying connection...');
self.emit('reconnecting', {
var reconnect_params = {
delay: self.retry_delay,
attempt: self.attempts,
error: error,
times_connected: self.times_connected,
total_retry_time: self.retry_totaltime
});
error: error
};
if (self.options.camel_case) {
reconnect_params.totalRetryTime = self.retry_totaltime;
reconnect_params.timesConnected = self.times_connected;
} else {
reconnect_params.total_retry_time = self.retry_totaltime;
reconnect_params.times_connected = self.times_connected;
}
self.emit('reconnecting', reconnect_params);
self.retry_totaltime += self.retry_delay;
self.attempts += 1;
@@ -529,12 +535,18 @@ RedisClient.prototype.connection_gone = function (why, error) {
}
if (typeof this.options.retry_strategy === 'function') {
this.retry_delay = this.options.retry_strategy({
var retry_params = {
attempt: this.attempts,
error: error,
total_retry_time: this.retry_totaltime,
times_connected: this.times_connected
});
error: error
};
if (this.options.camel_case) {
retry_params.totalRetryTime = this.retry_totaltime;
retry_params.timesConnected = this.times_connected;
} else {
retry_params.total_retry_time = this.retry_totaltime;
retry_params.times_connected = this.times_connected;
}
this.retry_delay = this.options.retry_strategy(retry_params);
if (typeof this.retry_delay !== 'number') {
// Pass individual error through
if (this.retry_delay instanceof Error) {
@@ -902,6 +914,72 @@ RedisClient.prototype.write = function (data) {
return;
};
Object.defineProperty(exports, 'debugMode', {
get: function () {
return this.debug_mode;
},
set: function (val) {
this.debug_mode = val;
}
});
// Don't officially expose the command_queue directly but only the length as read only variable
Object.defineProperty(RedisClient.prototype, 'command_queue_length', {
get: function () {
return this.command_queue.length;
}
});
Object.defineProperty(RedisClient.prototype, 'offline_queue_length', {
get: function () {
return this.offline_queue.length;
}
});
// Add support for camelCase by adding read only properties to the client
// All known exposed snack_case variables are added here
Object.defineProperty(RedisClient.prototype, 'retryDelay', {
get: function () {
return this.retry_delay;
}
});
Object.defineProperty(RedisClient.prototype, 'retryBackoff', {
get: function () {
return this.retry_backoff;
}
});
Object.defineProperty(RedisClient.prototype, 'commandQueueLength', {
get: function () {
return this.command_queue.length;
}
});
Object.defineProperty(RedisClient.prototype, 'offlineQueueLength', {
get: function () {
return this.offline_queue.length;
}
});
Object.defineProperty(RedisClient.prototype, 'shouldBuffer', {
get: function () {
return this.should_buffer;
}
});
Object.defineProperty(RedisClient.prototype, 'connectionId', {
get: function () {
return this.connection_id;
}
});
Object.defineProperty(RedisClient.prototype, 'serverInfo', {
get: function () {
return this.server_info;
}
});
exports.createClient = function () {
return new RedisClient(unifyOptions.apply(null, arguments));
};

View File

@@ -10,7 +10,7 @@ All documented and exposed API belongs in here
**********************************************/
// Redirect calls to the appropriate function and use to send arbitrary / not supported commands
RedisClient.prototype.send_command = function (command, args, callback) {
RedisClient.prototype.send_command = RedisClient.prototype.sendCommand = function (command, args, callback) {
// Throw to fail early instead of relying in order in this case
if (typeof command !== 'string') {
throw new Error('Wrong input type "' + (command !== null && command !== undefined ? command.constructor.name : command) + '" for command name');

View File

@@ -70,7 +70,7 @@ function pipeline_transaction_command (self, command, args, index, cb) {
});
}
Multi.prototype.exec_atomic = function exec_atomic (callback) {
Multi.prototype.exec_atomic = Multi.prototype.EXEC_ATOMIC = Multi.prototype.execAtomic = function exec_atomic (callback) {
if (this.queue.length < 2) {
return this.exec_batch(callback);
}

View File

@@ -41,8 +41,10 @@ function print (err, reply) {
}
}
var camelCase;
// Deep clone arbitrary objects with arrays. Can't handle cyclic structures (results in a range error)
// Any attribute with a non primitive value besides object and array will be passed by reference (e.g. Buffers, Maps, Functions)
// All capital letters are going to be replaced with a lower case letter and a underscore infront of it
function clone (obj) {
var copy;
if (Array.isArray(obj)) {
@@ -57,7 +59,14 @@ function clone (obj) {
var elems = Object.keys(obj);
var elem;
while (elem = elems.pop()) {
copy[elem] = clone(obj[elem]);
// Accept camelCase options and convert them to snack_case
var snack_case = elem.replace(/[A-Z][^A-Z]/g, '_$&').toLowerCase();
// If camelCase is detected, pass it to the client, so all variables are going to be camelCased
// There are no deep nested options objects yet, but let's handle this future proof
if (snack_case !== elem.toLowerCase()) {
camelCase = true;
}
copy[snack_case] = clone(obj[elem]);
}
return copy;
}
@@ -65,7 +74,12 @@ function clone (obj) {
}
function convenienceClone (obj) {
return clone(obj) || {};
camelCase = false;
obj = clone(obj) || {};
if (camelCase) {
obj.camel_case = true;
}
return obj;
}
function callbackOrEmit (self, callback, err, res) {

View File

@@ -166,8 +166,18 @@ describe('client authentication', function () {
client = redis.createClient.apply(null, args);
client.auth(auth);
client.on('ready', function () {
if (this.times_connected === 1) {
if (this.times_connected < 3) {
var interval = setInterval(function () {
if (client.commandQueueLength !== 0) {
return;
}
clearInterval(interval);
interval = null;
client.stream.destroy();
client.set('foo', 'bar');
client.get('foo'); // Errors would bubble
assert.strictEqual(client.offlineQueueLength, 2);
}, 1);
} else {
done();
}

View File

@@ -23,16 +23,16 @@ describe("The 'info' method", function () {
client.end(true);
});
it('update server_info after a info command', function (done) {
it('update serverInfo after a info command', function (done) {
client.set('foo', 'bar');
client.info();
client.select(2, function () {
assert.strictEqual(client.server_info.db2, undefined);
assert.strictEqual(client.serverInfo.db2, undefined);
});
client.set('foo', 'bar');
client.info();
setTimeout(function () {
assert.strictEqual(typeof client.server_info.db2, 'object');
assert.strictEqual(typeof client.serverInfo.db2, 'object');
done();
}, 30);
});

View File

@@ -132,12 +132,14 @@ describe('connection tests', function () {
describe('on lost connection', function () {
it('emit an error after max retry attempts and do not try to reconnect afterwards', function (done) {
var max_attempts = 3;
var maxAttempts = 3;
var options = {
parser: parser,
max_attempts: max_attempts
maxAttempts: maxAttempts
};
client = redis.createClient(options);
assert.strictEqual(client.retryBackoff, 1.7);
assert.strictEqual(client.retryDelay, 200);
assert.strictEqual(Object.keys(options).length, 2);
var calls = 0;
@@ -152,7 +154,7 @@ describe('connection tests', function () {
client.on('error', function (err) {
if (/Redis connection in broken state: maximum connection attempts.*?exceeded./.test(err.message)) {
process.nextTick(function () { // End is called after the error got emitted
assert.strictEqual(calls, max_attempts - 1);
assert.strictEqual(calls, maxAttempts - 1);
assert.strictEqual(client.emitted_end, true);
assert.strictEqual(client.connected, false);
assert.strictEqual(client.ready, false);
@@ -248,7 +250,7 @@ describe('connection tests', function () {
});
});
it('retry_strategy used to reconnect with individual error', function (done) {
it('retryStrategy used to reconnect with individual error', function (done) {
var text = '';
var unhookIntercept = intercept(function (data) {
text += data;
@@ -256,8 +258,8 @@ describe('connection tests', function () {
});
var end = helper.callFuncAfter(done, 2);
client = redis.createClient({
retry_strategy: function (options) {
if (options.total_retry_time > 150) {
retryStrategy: function (options) {
if (options.totalRetryTime > 150) {
client.set('foo', 'bar', function (err, res) {
assert.strictEqual(err.message, 'Connection timeout');
end();
@@ -267,8 +269,8 @@ describe('connection tests', function () {
}
return Math.min(options.attempt * 25, 200);
},
max_attempts: 5,
retry_max_delay: 123,
maxAttempts: 5,
retryMaxDelay: 123,
port: 9999
});

View File

@@ -571,7 +571,7 @@ describe("The 'multi' method", function () {
test = true;
};
multi.set('baz', 'binary');
multi.exec_atomic();
multi.EXEC_ATOMIC();
assert(test);
});

View File

@@ -30,6 +30,7 @@ describe('The node_redis client', function () {
it('check if all options got copied properly', function (done) {
client.selected_db = 2;
var client2 = client.duplicate();
assert.strictEqual(client.connectionId + 1, client2.connection_id);
assert.strictEqual(client2.selected_db, 2);
assert(client.connected);
assert(!client2.connected);
@@ -360,7 +361,7 @@ describe('The node_redis client', function () {
client.on('error', function (err) {
assert.strictEqual(err.message, 'SET can\'t be processed. The connection has already been closed.');
assert.strictEqual(err.command, 'SET');
assert.strictEqual(client.offline_queue.length, 0);
assert.strictEqual(client.offline_queue_length, 0);
done();
});
setTimeout(function () {
@@ -966,7 +967,7 @@ describe('The node_redis client', function () {
multi.set('foo' + (i + 2), 'bar' + (i + 2));
}
multi.exec();
assert.equal(client.command_queue.length, 15);
assert.equal(client.command_queue_length, 15);
helper.killConnection(client);
});

View File

@@ -11,7 +11,7 @@ describe('utils.js', function () {
it('ignore the object prototype and clone a nested array / object', function () {
var obj = {
a: [null, 'foo', ['bar'], {
"I'm special": true
"i'm special": true
}],
number: 5,
fn: function noop () {}
@@ -22,13 +22,28 @@ describe('utils.js', function () {
assert(typeof clone.fn === 'function');
});
it('replace faulty values with an empty object as return value', function () {
it('replace falsy values with an empty object as return value', function () {
var a = utils.clone();
var b = utils.clone(null);
assert.strictEqual(Object.keys(a).length, 0);
assert.strictEqual(Object.keys(b).length, 0);
});
it('transform camelCase options to snack_case and add the camel_case option', function () {
var a = utils.clone({
optionOneTwo: true,
retryStrategy: false,
nested: {
onlyContainCamelCaseOnce: true
}
});
assert.strictEqual(Object.keys(a).length, 4);
assert.strictEqual(a.option_one_two, true);
assert.strictEqual(a.retry_strategy, false);
assert.strictEqual(a.camel_case, true);
assert.strictEqual(Object.keys(a.nested).length, 1);
});
it('throws on circular data', function () {
try {
var a = {};