From 2a33fc78447699f3b2b1bd894cd59fef19ad6a1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zihua=20=E5=AD=90=E9=AA=85?= Date: Sun, 10 Apr 2016 23:49:42 +0800 Subject: [PATCH] feat: add stringNumbers option to return numbers as JavaScript strings (#282) Closes #273 --- API.md | 83 +++++++++++++++++++++---------- lib/redis.js | 7 ++- lib/redis/parser.js | 1 + test/functional/string_numbers.js | 52 +++++++++++++++++++ 4 files changed, 117 insertions(+), 26 deletions(-) create mode 100644 test/functional/string_numbers.js diff --git a/API.md b/API.md index 23066791..ab0898e2 100644 --- a/API.md +++ b/API.md @@ -1,4 +1,5 @@ ## Classes +
Redis[EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter)
@@ -7,7 +8,9 @@
Commander
+ ## Members +
defaultOptions

Default options

@@ -16,26 +19,29 @@

Default options

+ + ## Redis ⇐ [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter) **Kind**: global class **Extends:** [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter), [Commander](#Commander) * [Redis](#Redis) ⇐ [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter) - * [new Redis([port], [host], [options])](#new_Redis_new) - * _instance_ - * [.connect(callback)](#Redis+connect) ⇒ Promise - * [.disconnect()](#Redis+disconnect) - * ~~[.end()](#Redis+end)~~ - * [.duplicate()](#Redis+duplicate) - * [.monitor([callback])](#Redis+monitor) - * [.getBuiltinCommands()](#Commander+getBuiltinCommands) ⇒ Array.<string> - * [.createBuiltinCommand(commandName)](#Commander+createBuiltinCommand) ⇒ object - * [.defineCommand(name, definition)](#Commander+defineCommand) - * _static_ - * ~~[.createClient()](#Redis.createClient)~~ + * [new Redis([port], [host], [options])](#new_Redis_new) + * _instance_ + * [.connect(callback)](#Redis+connect) ⇒ Promise + * [.disconnect()](#Redis+disconnect) + * ~~[.end()](#Redis+end)~~ + * [.duplicate()](#Redis+duplicate) + * [.monitor([callback])](#Redis+monitor) + * [.getBuiltinCommands()](#Commander+getBuiltinCommands) ⇒ Array.<string> + * [.createBuiltinCommand(commandName)](#Commander+createBuiltinCommand) ⇒ object + * [.defineCommand(name, definition)](#Commander+defineCommand) + * _static_ + * ~~[.createClient()](#Redis.createClient)~~ + ### new Redis([port], [host], [options]) Creates a Redis instance @@ -63,6 +69,7 @@ Creates a Redis instance | [options.retryStrategy] | function | | See "Quick Start" section | | [options.reconnectOnError] | function | | See "Quick Start" section | | [options.readOnly] | boolean | false | Enable READONLY mode for the connection. Only available for cluster mode. | +| [options.stringNumbers] | boolean | false | Force numbers to be always returned as JavaScript strings. This option is necessary when dealing with big numbers (exceed the [-2^53, +2^53] range). Notice that when this option is enabled, the JavaScript parser will be used even "hiredis" is specified because only JavaScript parser supports this feature for the time being. | **Example** ```js @@ -80,6 +87,7 @@ var urlRedis2 = new Redis('//localhost:6379'); var authedRedis = new Redis(6380, '192.168.100.1', { password: 'password' }); ``` + ### redis.connect(callback) ⇒ Promise Create a connection to Redis. This method will be invoked automatically when creating a new Redis instance. @@ -92,6 +100,7 @@ This method will be invoked automatically when creating a new Redis instance. | callback | function | + ### redis.disconnect() Disconnect from Redis. @@ -102,6 +111,7 @@ If you want to wait for the pending replies, use Redis#quit instead. **Kind**: instance method of [Redis](#Redis) **Access:** public + ### ~~redis.end()~~ ***Deprecated*** @@ -109,6 +119,7 @@ Disconnect from Redis. **Kind**: instance method of [Redis](#Redis) + ### redis.duplicate() Create a new instance with the same options as the current one. @@ -120,6 +131,7 @@ var redis = new Redis(6380); var anotherRedis = redis.duplicate(); ``` + ### redis.monitor([callback]) Listen for all requests received by the server in real time. @@ -152,6 +164,7 @@ redis.monitor().then(function (monitor) { }); ``` + ### redis.getBuiltinCommands() ⇒ Array.<string> Return supported builtin commands @@ -159,6 +172,7 @@ Return supported builtin commands **Returns**: Array.<string> - command list **Access:** public + ### redis.createBuiltinCommand(commandName) ⇒ object Create a builtin command @@ -171,6 +185,7 @@ Create a builtin command | commandName | string | command name | + ### redis.defineCommand(name, definition) Define a custom command using lua script @@ -184,6 +199,7 @@ Define a custom command using lua script | [definition.numberOfKeys] | number | | the number of keys. If omit, you have to pass the number of keys as the first argument every time you invoke the command | + ### ~~Redis.createClient()~~ ***Deprecated*** @@ -191,21 +207,23 @@ Create a Redis instance **Kind**: static method of [Redis](#Redis) + ## Cluster ⇐ [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter) **Kind**: global class **Extends:** [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter), [Commander](#Commander) * [Cluster](#Cluster) ⇐ [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter) - * [new Cluster(startupNodes, options)](#new_Cluster_new) - * [.connect()](#Cluster+connect) ⇒ Promise - * [.disconnect()](#Cluster+disconnect) - * [.nodes([role])](#Cluster+nodes) ⇒ [Array.<Redis>](#Redis) - * [.getBuiltinCommands()](#Commander+getBuiltinCommands) ⇒ Array.<string> - * [.createBuiltinCommand(commandName)](#Commander+createBuiltinCommand) ⇒ object - * [.defineCommand(name, definition)](#Commander+defineCommand) - * *[.sendCommand()](#Commander+sendCommand)* + * [new Cluster(startupNodes, options)](#new_Cluster_new) + * [.connect()](#Cluster+connect) ⇒ Promise + * [.disconnect()](#Cluster+disconnect) + * [.nodes([role])](#Cluster+nodes) ⇒ [Array.<Redis>](#Redis) + * [.getBuiltinCommands()](#Commander+getBuiltinCommands) ⇒ Array.<string> + * [.createBuiltinCommand(commandName)](#Commander+createBuiltinCommand) ⇒ object + * [.defineCommand(name, definition)](#Commander+defineCommand) + * *[.sendCommand()](#Commander+sendCommand)* + ### new Cluster(startupNodes, options) Creates a Redis Cluster instance @@ -225,18 +243,21 @@ Creates a Redis Cluster instance | [options.redisOptions] | Object | | Passed to the constructor of `Redis`. | + ### cluster.connect() ⇒ Promise Connect to a cluster **Kind**: instance method of [Cluster](#Cluster) **Access:** public + ### cluster.disconnect() Disconnect from every node in the cluster. **Kind**: instance method of [Cluster](#Cluster) **Access:** public + ### cluster.nodes([role]) ⇒ [Array.<Redis>](#Redis) Get nodes with the specified role @@ -249,6 +270,7 @@ Get nodes with the specified role | [role] | string | "all" | role, "master", "slave" or "all" | + ### cluster.getBuiltinCommands() ⇒ Array.<string> Return supported builtin commands @@ -256,6 +278,7 @@ Return supported builtin commands **Returns**: Array.<string> - command list **Access:** public + ### cluster.createBuiltinCommand(commandName) ⇒ object Create a builtin command @@ -268,6 +291,7 @@ Create a builtin command | commandName | string | command name | + ### cluster.defineCommand(name, definition) Define a custom command using lua script @@ -281,6 +305,7 @@ Define a custom command using lua script | [definition.numberOfKeys] | number | | the number of keys. If omit, you have to pass the number of keys as the first argument every time you invoke the command | + ### *cluster.sendCommand()* Send a command @@ -288,17 +313,19 @@ Send a command **Overrides:** [sendCommand](#Commander+sendCommand) **Access:** public + ## Commander **Kind**: global class * [Commander](#Commander) - * [new Commander()](#new_Commander_new) - * [.getBuiltinCommands()](#Commander+getBuiltinCommands) ⇒ Array.<string> - * [.createBuiltinCommand(commandName)](#Commander+createBuiltinCommand) ⇒ object - * [.defineCommand(name, definition)](#Commander+defineCommand) - * *[.sendCommand()](#Commander+sendCommand)* + * [new Commander()](#new_Commander_new) + * [.getBuiltinCommands()](#Commander+getBuiltinCommands) ⇒ Array.<string> + * [.createBuiltinCommand(commandName)](#Commander+createBuiltinCommand) ⇒ object + * [.defineCommand(name, definition)](#Commander+defineCommand) + * *[.sendCommand()](#Commander+sendCommand)* + ### new Commander() Commander @@ -310,6 +337,7 @@ This is the base class of Redis, Redis.Cluster and Pipeline | [options.showFriendlyErrorStack] | boolean | false | Whether to show a friendly error stack. Will decrease the performance significantly. | + ### commander.getBuiltinCommands() ⇒ Array.<string> Return supported builtin commands @@ -317,6 +345,7 @@ Return supported builtin commands **Returns**: Array.<string> - command list **Access:** public + ### commander.createBuiltinCommand(commandName) ⇒ object Create a builtin command @@ -329,6 +358,7 @@ Create a builtin command | commandName | string | command name | + ### commander.defineCommand(name, definition) Define a custom command using lua script @@ -342,18 +372,21 @@ Define a custom command using lua script | [definition.numberOfKeys] | number | | the number of keys. If omit, you have to pass the number of keys as the first argument every time you invoke the command | + ### *commander.sendCommand()* Send a command **Kind**: instance abstract method of [Commander](#Commander) **Access:** public + ## defaultOptions Default options **Kind**: global variable **Access:** protected + ## defaultOptions Default options diff --git a/lib/redis.js b/lib/redis.js index d21ef97f..99bfd1fa 100644 --- a/lib/redis.js +++ b/lib/redis.js @@ -74,6 +74,10 @@ var ScanStream = require('./scan_stream'); * @param {function} [options.reconnectOnError] - See "Quick Start" section * @param {boolean} [options.readOnly=false] - Enable READONLY mode for the connection. * Only available for cluster mode. + * @param {boolean} [options.stringNumbers=false] - Force numbers to be always returned as JavaScript + * strings. This option is necessary when dealing with big numbers (exceed the [-2^53, +2^53] range). + * Notice that when this option is enabled, the JavaScript parser will be used even "hiredis" is specified + * because only JavaScript parser supports this feature for the time being. * @extends [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter) * @extends Commander * @example @@ -169,7 +173,8 @@ Redis.defaultOptions = { lazyConnect: false, keyPrefix: '', reconnectOnError: null, - readOnly: false + readOnly: false, + stringNumbers: false }; Redis.prototype.resetCommandQueue = function () { diff --git a/lib/redis/parser.js b/lib/redis/parser.js index 433ebbd8..39c3ec66 100644 --- a/lib/redis/parser.js +++ b/lib/redis/parser.js @@ -19,6 +19,7 @@ exports.initParser = function () { this.replyParser = new Parser({ name: this.options.parser, + stringNumbers: this.options.stringNumbers, returnBuffers: true, returnError: function (err) { _this.returnError(new ReplyError(err.message)); diff --git a/test/functional/string_numbers.js b/test/functional/string_numbers.js new file mode 100644 index 00000000..63fbe38c --- /dev/null +++ b/test/functional/string_numbers.js @@ -0,0 +1,52 @@ +'use strict'; + +var MAX_NUMBER = 9007199254740991; // Number.MAX_SAFE_INTEGER + +describe('stringNumbers', function () { + context('enabled', function () { + it('returns numbers as strings', function (done) { + var redis = new Redis({ + stringNumbers: true + }); + + var pending = 0; + + redis.set('foo', MAX_NUMBER); + redis.incr('foo', check('9007199254740992')); + redis.incr('foo', check('9007199254740993')); + redis.incr('foo', check('9007199254740994')); + + // also works for small interger + redis.set('foo', 123); + redis.incr('foo', check('124')); + + // and floats + redis.set('foo', 123.23); + redis.incrbyfloat('foo', 1.2, check('124.43')); + + function check(expected) { + pending += 1; + return function (err, res) { + expect(res).to.eql(expected); + if (!--pending) { + redis.disconnect(); + done(); + } + }; + } + }); + }); + + context('disabled', function () { + it('returns numbers', function (done) { + var redis = new Redis(); + + redis.set('foo', '123'); + redis.incr('foo', function (err, res) { + expect(res).to.eql(124); + redis.disconnect(); + done(); + }); + }); + }); +});