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

Add string_numbers option to handle very big numbers

This commit is contained in:
Ruben Bridgewater
2016-03-27 03:06:32 +02:00
parent 2e68a7a270
commit 0c5947be51
8 changed files with 62 additions and 89 deletions

View File

@@ -184,6 +184,7 @@ If the redis server runs on the same machine as the client consider using unix s
* `path`: *null*; The unix socket string to connect to * `path`: *null*; The unix socket string to connect to
* `url`: *null*; The redis url to connect to (`[redis:]//[user][:password@][host][:port][/db-number][?db=db-number[&password=bar[&option=value]]]` For more info check [IANA](http://www.iana.org/assignments/uri-schemes/prov/redis)) * `url`: *null*; The redis url to connect to (`[redis:]//[user][:password@][host][:port][/db-number][?db=db-number[&password=bar[&option=value]]]` For more info check [IANA](http://www.iana.org/assignments/uri-schemes/prov/redis))
* `parser`: *hiredis*; Which Redis protocol reply parser to use. If `hiredis` is not installed it will fallback to `javascript`. * `parser`: *hiredis*; Which Redis protocol reply parser to use. If `hiredis` is not installed it will fallback to `javascript`.
* `string_numbers`: *boolean*; pass true to get numbers back as strings instead of js numbers. This is necessary if you want to handle big numbers (above `Number.MAX_SAFE_INTEGER` === 2^53). If passed, the js parser is automatically choosen as parser no matter if the parser is set to hiredis or not, as hiredis is not capable of doing this.
* `return_buffers`: *false*; If set to `true`, then all replies will be sent to callbacks as Buffers instead of Strings. * `return_buffers`: *false*; If set to `true`, then all replies will be sent to callbacks as Buffers instead of Strings.
* `detect_buffers`: *false*; If set to `true`, then replies will be sent to callbacks as Buffers. Please be aware that this can't work properly with the pubsub mode. A subscriber has to either always return strings or buffers. * `detect_buffers`: *false*; If set to `true`, then replies will be sent to callbacks as Buffers. Please be aware that this can't work properly with the pubsub mode. A subscriber has to either always return strings or buffers.
if any of the input arguments to the original command were Buffers. if any of the input arguments to the original command were Buffers.

View File

@@ -9,6 +9,7 @@ Features
- All commands that were send after a connection loss are now going to be send after reconnecting - All commands that were send after a connection loss are now going to be send after reconnecting
- Activating monitor mode does now work together with arbitrary commands including pub sub mode - Activating monitor mode does now work together with arbitrary commands including pub sub mode
- Pub sub mode is completly rewritten and all known issues fixed - Pub sub mode is completly rewritten and all known issues fixed
- Added `string_numbers` option to get back strings instead of numbers
Bugfixes Bugfixes

View File

@@ -147,8 +147,8 @@ function RedisClient (options, stream) {
returnReply: function (data) { returnReply: function (data) {
self.return_reply(data); self.return_reply(data);
}, },
returnError: function (data) { returnError: function (err) {
self.return_error(data); self.return_error(err);
}, },
returnFatalError: function (err) { returnFatalError: function (err) {
// Error out all fired commands. Otherwise they might rely on faulty data. We have to reconnect to get in a working state again // Error out all fired commands. Otherwise they might rely on faulty data. We have to reconnect to get in a working state again
@@ -157,7 +157,8 @@ function RedisClient (options, stream) {
self.return_error(err); self.return_error(err);
}, },
returnBuffers: this.buffers, returnBuffers: this.buffers,
name: options.parser name: options.parser,
stringNumbers: options.string_numbers
}); });
this.create_stream(); this.create_stream();
// The listeners will not be attached right away, so let's print the deprecation message while the listener is attached // The listeners will not be attached right away, so let's print the deprecation message while the listener is attached

View File

@@ -26,8 +26,8 @@
}, },
"dependencies": { "dependencies": {
"double-ended-queue": "^2.1.0-0", "double-ended-queue": "^2.1.0-0",
"redis-commands": "^1.0.1", "redis-commands": "^1.1.0",
"redis-parser": "^1.1.0" "redis-parser": "^1.2.0"
}, },
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"

View File

@@ -10,101 +10,65 @@ describe("The 'incr' method", function () {
helper.allTests(function (parser, ip, args) { helper.allTests(function (parser, ip, args) {
describe('using ' + parser + ' and ' + ip, function () { describe('using ' + parser + ' and ' + ip, function () {
var key = 'sequence';
describe('when not connected', function () { describe('when connected and a value in Redis', function () {
var client; var client;
var key = 'ABOVE_SAFE_JAVASCRIPT_INTEGER';
beforeEach(function (done) { var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; // Backwards compatible
client = redis.createClient.apply(null, args);
client.once('ready', function () {
client.set(key, '9007199254740992', function (err, res) {
helper.isNotError()(err, res);
client.quit();
});
});
client.on('end', done);
});
afterEach(function () { afterEach(function () {
client.end(true); client.end(true);
}); });
it('reports an error', function (done) { /*
client.incr(function (err, res) { Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1 === 9007199254740991
assert(err.message.match(/The connection has already been closed/));
done();
});
});
});
describe('when connected and a value in Redis', function () { 9007199254740992 -> 9007199254740992
var client; 9007199254740993 -> 9007199254740992
9007199254740994 -> 9007199254740994
before(function (done) { 9007199254740995 -> 9007199254740996
/* 9007199254740996 -> 9007199254740996
9007199254740992 -> 9007199254740992 9007199254740997 -> 9007199254740996
9007199254740993 -> 9007199254740992 ...
9007199254740994 -> 9007199254740994 */
9007199254740995 -> 9007199254740996 it('count above the safe integers as numbers', function (done) {
9007199254740996 -> 9007199254740996
9007199254740997 -> 9007199254740996
*/
client = redis.createClient.apply(null, args); client = redis.createClient.apply(null, args);
client.once('error', done); // Set a value to the maximum safe allowed javascript number (2^53) - 1
client.once('ready', function () { client.set(key, MAX_SAFE_INTEGER, helper.isNotError());
client.set(key, '9007199254740992', function (err, res) { client.INCR(key, helper.isNumber(MAX_SAFE_INTEGER + 1));
helper.isNotError()(err, res); client.INCR(key, helper.isNumber(MAX_SAFE_INTEGER + 2));
done(); client.INCR(key, helper.isNumber(MAX_SAFE_INTEGER + 3));
}); client.INCR(key, helper.isNumber(MAX_SAFE_INTEGER + 4));
}); client.INCR(key, helper.isNumber(MAX_SAFE_INTEGER + 5));
});
after(function () {
client.end(true);
});
it('changes the last digit from 2 to 3', function (done) {
client.INCR(key, function (err, res) { client.INCR(key, function (err, res) {
helper.isString('9007199254740993')(err, res); helper.isNumber(MAX_SAFE_INTEGER + 6)(err, res);
done(err); assert.strictEqual(typeof res, 'number');
}); });
client.INCR(key, helper.isNumber(MAX_SAFE_INTEGER + 7));
client.INCR(key, helper.isNumber(MAX_SAFE_INTEGER + 8));
client.INCR(key, helper.isNumber(MAX_SAFE_INTEGER + 9));
client.INCR(key, helper.isNumber(MAX_SAFE_INTEGER + 10, done));
}); });
describe('and we call it again', function () { it('count above the safe integers as strings', function (done) {
it('changes the last digit from 3 to 4', function (done) { args[2].string_numbers = true;
client.incr(key, function (err, res) { client = redis.createClient.apply(null, args);
helper.isString('9007199254740994')(err, res); // Set a value to the maximum safe allowed javascript number (2^53)
done(err); client.set(key, MAX_SAFE_INTEGER, helper.isNotError());
}); client.incr(key, helper.isString('9007199254740992'));
}); client.incr(key, helper.isString('9007199254740993'));
client.incr(key, helper.isString('9007199254740994'));
describe('and again', function () { client.incr(key, helper.isString('9007199254740995'));
it('changes the last digit from 4 to 5', function (done) { client.incr(key, helper.isString('9007199254740996'));
client.incr(key, function (err, res) { client.incr(key, function (err, res) {
helper.isString('9007199254740995')(err, res); helper.isString('9007199254740997')(err, res);
done(err); assert.strictEqual(typeof res, 'string');
});
});
describe('and again', function () {
it('changes the last digit from 5 to 6', function (done) {
client.incr(key, function (err, res) {
helper.isString('9007199254740996')(err, res);
done(err);
});
});
describe('and again', function () {
it('changes the last digit from 6 to 7', function (done) {
client.incr(key, function (err, res) {
helper.isString('9007199254740997')(err, res);
done(err);
});
});
});
});
}); });
client.incr(key, helper.isString('9007199254740998'));
client.incr(key, helper.isString('9007199254740999'));
client.incr(key, helper.isString('9007199254741000'));
client.incr(key, helper.isString('9007199254741001', done));
}); });
}); });
}); });

View File

@@ -34,7 +34,7 @@ describe("The 'script' method", function () {
}); });
it('allows a loaded script to be evaluated', function (done) { it('allows a loaded script to be evaluated', function (done) {
client.evalsha(commandSha, 0, helper.isString('99', done)); client.evalsha(commandSha, 0, helper.isNumber(99, done));
}); });
it('allows a script to be loaded as part of a chained transaction', function (done) { it('allows a script to be loaded as part of a chained transaction', function (done) {

View File

@@ -38,7 +38,9 @@ describe('detect_buffers', function () {
}); });
it('returns a string when executed as part of transaction', function (done) { it('returns a string when executed as part of transaction', function (done) {
client.multi().get('string key 1').exec(helper.isString('string value', done)); client.multi().get('string key 1').exec(function (err, res) {
helper.isString('string value', done)(err, res[0]);
});
}); });
}); });

View File

@@ -59,9 +59,13 @@ module.exports = {
}; };
}, },
isString: function (str, done) { isString: function (str, done) {
str = '' + str; // Make sure it's a string
return function (err, results) { return function (err, results) {
assert.strictEqual(null, err, "expected string '" + str + "', got error: " + err); assert.strictEqual(null, err, "expected string '" + str + "', got error: " + err);
assert.equal(str, results, str + ' does not match ' + results); if (Buffer.isBuffer(results)) { // If options are passed to return either strings or buffers...
results = results.toString();
}
assert.strictEqual(str, results, str + ' does not match ' + results);
if (done) done(); if (done) done();
}; };
}, },