diff --git a/README.md b/README.md index f0d83e20c4..2db31174cb 100644 --- a/README.md +++ b/README.md @@ -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 * `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`. +* `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. * `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. diff --git a/changelog.md b/changelog.md index 69c5c6d39c..687e3bc875 100644 --- a/changelog.md +++ b/changelog.md @@ -9,6 +9,7 @@ Features - 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 - Pub sub mode is completly rewritten and all known issues fixed +- Added `string_numbers` option to get back strings instead of numbers Bugfixes diff --git a/index.js b/index.js index a306cf0100..5245f824c1 100644 --- a/index.js +++ b/index.js @@ -147,8 +147,8 @@ function RedisClient (options, stream) { returnReply: function (data) { self.return_reply(data); }, - returnError: function (data) { - self.return_error(data); + returnError: function (err) { + self.return_error(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 @@ -157,7 +157,8 @@ function RedisClient (options, stream) { self.return_error(err); }, returnBuffers: this.buffers, - name: options.parser + name: options.parser, + stringNumbers: options.string_numbers }); this.create_stream(); // The listeners will not be attached right away, so let's print the deprecation message while the listener is attached diff --git a/package.json b/package.json index 7e4d234867..92ac9fd7d2 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,8 @@ }, "dependencies": { "double-ended-queue": "^2.1.0-0", - "redis-commands": "^1.0.1", - "redis-parser": "^1.1.0" + "redis-commands": "^1.1.0", + "redis-parser": "^1.2.0" }, "engines": { "node": ">=0.10.0" diff --git a/test/commands/incr.spec.js b/test/commands/incr.spec.js index ab385ecbbf..f1f1e78ee8 100644 --- a/test/commands/incr.spec.js +++ b/test/commands/incr.spec.js @@ -10,101 +10,65 @@ describe("The 'incr' method", function () { helper.allTests(function (parser, ip, args) { describe('using ' + parser + ' and ' + ip, function () { - var key = 'sequence'; - describe('when not connected', function () { - var client; + describe('when connected and a value in Redis', function () { - beforeEach(function (done) { - 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); - }); + var client; + var key = 'ABOVE_SAFE_JAVASCRIPT_INTEGER'; + var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; // Backwards compatible afterEach(function () { client.end(true); }); - it('reports an error', function (done) { - client.incr(function (err, res) { - assert(err.message.match(/The connection has already been closed/)); - done(); - }); - }); - }); - - describe('when connected and a value in Redis', function () { - var client; - - before(function (done) { - /* - 9007199254740992 -> 9007199254740992 - 9007199254740993 -> 9007199254740992 - 9007199254740994 -> 9007199254740994 - 9007199254740995 -> 9007199254740996 - 9007199254740996 -> 9007199254740996 - 9007199254740997 -> 9007199254740996 - */ + /* + Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1 === 9007199254740991 + + 9007199254740992 -> 9007199254740992 + 9007199254740993 -> 9007199254740992 + 9007199254740994 -> 9007199254740994 + 9007199254740995 -> 9007199254740996 + 9007199254740996 -> 9007199254740996 + 9007199254740997 -> 9007199254740996 + ... + */ + it('count above the safe integers as numbers', function (done) { client = redis.createClient.apply(null, args); - client.once('error', done); - client.once('ready', function () { - client.set(key, '9007199254740992', function (err, res) { - helper.isNotError()(err, res); - done(); - }); - }); - }); - - after(function () { - client.end(true); - }); - - it('changes the last digit from 2 to 3', function (done) { + // Set a value to the maximum safe allowed javascript number (2^53) - 1 + client.set(key, MAX_SAFE_INTEGER, helper.isNotError()); + client.INCR(key, helper.isNumber(MAX_SAFE_INTEGER + 1)); + client.INCR(key, helper.isNumber(MAX_SAFE_INTEGER + 2)); + 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)); client.INCR(key, function (err, res) { - helper.isString('9007199254740993')(err, res); - done(err); + helper.isNumber(MAX_SAFE_INTEGER + 6)(err, res); + 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('changes the last digit from 3 to 4', function (done) { - client.incr(key, function (err, res) { - helper.isString('9007199254740994')(err, res); - done(err); - }); - }); - - describe('and again', function () { - it('changes the last digit from 4 to 5', function (done) { - client.incr(key, function (err, res) { - helper.isString('9007199254740995')(err, res); - done(err); - }); - }); - - 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); - }); - }); - }); - }); + it('count above the safe integers as strings', function (done) { + args[2].string_numbers = true; + client = redis.createClient.apply(null, args); + // Set a value to the maximum safe allowed javascript number (2^53) + 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')); + client.incr(key, helper.isString('9007199254740995')); + client.incr(key, helper.isString('9007199254740996')); + client.incr(key, function (err, res) { + helper.isString('9007199254740997')(err, res); + assert.strictEqual(typeof res, 'string'); }); + 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)); }); }); }); diff --git a/test/commands/script.spec.js b/test/commands/script.spec.js index 25a44930b5..2e29ad553a 100644 --- a/test/commands/script.spec.js +++ b/test/commands/script.spec.js @@ -34,7 +34,7 @@ describe("The 'script' method", function () { }); 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) { diff --git a/test/detect_buffers.spec.js b/test/detect_buffers.spec.js index 59f6f6ae20..82f1aa99e3 100644 --- a/test/detect_buffers.spec.js +++ b/test/detect_buffers.spec.js @@ -38,7 +38,9 @@ describe('detect_buffers', function () { }); 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]); + }); }); }); diff --git a/test/helper.js b/test/helper.js index 5564a49c5e..f28568b500 100644 --- a/test/helper.js +++ b/test/helper.js @@ -59,9 +59,13 @@ module.exports = { }; }, isString: function (str, done) { + str = '' + str; // Make sure it's a string return function (err, results) { 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(); }; },