diff --git a/benchmarks/multi_bench.js b/benchmarks/multi_bench.js index f5950e642b..a8fe687781 100644 --- a/benchmarks/multi_bench.js +++ b/benchmarks/multi_bench.js @@ -82,7 +82,7 @@ Test.prototype.newClient = function (id) { console.log([ `clients: ${numClients}`, `NodeJS: ${process.versions.node}`, - `Redis: ${newClient.serverInfo.redis_version}`, + `Redis: ${newClient.serverInfo.server.redis_version}`, `connected by: ${clientOptions.path ? 'socket' : 'tcp'}` ].join(', ')) versionsLogged = true diff --git a/changelog.md b/changelog.md index d77f279757..bd49478b3f 100644 --- a/changelog.md +++ b/changelog.md @@ -47,6 +47,8 @@ Breaking Changes - Changed the name of two functions - `restore-asking` is now `restore_asking_` - `host:` is now `host` +- Changed the `serverInfo` into a nested object and to parse numbers +- Changed the `serverInfo.versions` to `serverInfo.version` - Using `.end` without the flush parameter is now going to throw an TypeError - Only emit ready when all commands were truly send to Redis diff --git a/index.js b/index.js index 9beea81b94..32c2cc9ef3 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,6 @@ 'use strict' -// TODO: Replace all `Error` with `RedisError` and improve errors in general +// TODO: Improve errors in general // We have to replace the error codes and make them coherent. // We also have to use InterruptError s instead of AbortError s. // The Error messages might be improved as well. diff --git a/lib/individualCommands.js b/lib/individualCommands.js index 43a0224f0f..23b559d318 100644 --- a/lib/individualCommands.js +++ b/lib/individualCommands.js @@ -116,36 +116,54 @@ Multi.prototype.quit = function quit () { function infoCallback (client) { return function (err, res) { if (err) { - client.serverInfo = {} return err } - const obj = {} - const lines = res.toString().split('\r\n') - var line, parts, subParts + if (typeof res !== 'string') { + res = res.toString() + } - for (var i = 0; i < lines.length; i++) { - parts = lines[i].split(':') - if (parts[1]) { - if (parts[0].indexOf('db') === 0) { - subParts = parts[1].split(',') - obj[parts[0]] = {} - for (line = subParts.pop(); line !== undefined; line = subParts.pop()) { - line = line.split('=') - obj[parts[0]][line[0]] = +line[1] + const obj = {} + const lines = res.split('\r\n') + var topic = '' + + while (lines.length) { + const parts = lines.shift().split(':') + const key = parts[0] + if (parts.length === 1) { + if (key !== '') { + topic = key[2].toLowerCase() + key.substr(3) + obj[topic] = {} + } + } else { + const value = parts[1] + const part = obj[topic] + if (value === '') { + part[key] = '' + } else if (topic === 'keyspace' || topic === 'commandstats') { + const subParts = value.split(',') + part[key] = {} + while (subParts.length) { + const line = subParts.shift().split('=') + part[key][line[0]] = +line[1] } } else { - obj[parts[0]] = parts[1] + const num = +value + // A fast Number.isNaN check + // eslint-disable-next-line no-self-compare + if (num === num) { + part[key] = num + } else { + part[key] = value + } } } } - obj.versions = [] - if (obj.redis_version) { - obj.redis_version.split('.').forEach((num) => { - obj.versions.push(+num) - }) + + if (obj.server && obj.server.redis_version) { + obj.server.version = obj.server.redis_version.split('.').map(Number) } - // Expose info key/values to users + client.serverInfo = obj return res } diff --git a/lib/readyHandler.js b/lib/readyHandler.js index 1ff96424b7..3a6e4dedf4 100644 --- a/lib/readyHandler.js +++ b/lib/readyHandler.js @@ -126,10 +126,12 @@ function readyCheck (client) { return } - if (!client.serverInfo.loading || client.serverInfo.loading === '0') { + const persistence = client.serverInfo.persistence + if (persistence === undefined || persistence.loading === undefined || persistence.loading === 0) { // If the master_link_status exists but the link is not up, try again after 50 ms - if (client.serverInfo.master_link_status && client.serverInfo.master_link_status !== 'up') { - client.serverInfo.loading_eta_seconds = 0.05 + const replication = client.serverInfo.replication + if (replication && typeof replication.master_link_status === 'string' && replication.master_link_status !== 'up') { + persistence.loading_eta_seconds = 0.05 } else { // Eta loading should change debug('Redis server ready.') @@ -138,7 +140,7 @@ function readyCheck (client) { } } - var retryTime = +client.serverInfo.loading_eta_seconds * 1000 + var retryTime = +persistence.loading_eta_seconds * 1000 if (retryTime > 1000) { retryTime = 1000 } diff --git a/test/auth.spec.js b/test/auth.spec.js index 4b58dad949..838536b599 100644 --- a/test/auth.spec.js +++ b/test/auth.spec.js @@ -80,10 +80,11 @@ if (process.platform !== 'win32') { // Set a key so the used database is returned in the info command promises.push(client.set('foo', 'bar')) promises.push(client.get('foo')) - assert.strictEqual(client.serverInfo.db2, undefined) + const space = client.serverInfo.keyspace + assert.strictEqual(space && space.db2, undefined) // Using the info command should update the serverInfo promises.push(client.info().then(() => { - assert(typeof client.serverInfo.db2 === 'object') + assert.strictEqual(typeof client.serverInfo.keyspace.db2, 'object') })) promises.push(client.flushdb()) return Promise.all(promises).then(() => done()) diff --git a/test/commands/info.spec.js b/test/commands/info.spec.js index 096ff7d2b0..b65ec48190 100644 --- a/test/commands/info.spec.js +++ b/test/commands/info.spec.js @@ -22,11 +22,11 @@ describe('The \'info\' method', () => { it('update serverInfo after a info command', () => { client.set('foo', 'bar') return client.info().then(() => { - assert.strictEqual(client.serverInfo.db2, undefined) + assert.strictEqual(client.serverInfo.keyspace.db2, undefined) client.select(2) client.set('foo', 'bar') return client.info().then(() => { - assert.strictEqual(typeof client.serverInfo.db2, 'object') + assert.strictEqual(typeof client.serverInfo.keyspace.db2, 'object') }) }) }) @@ -35,14 +35,15 @@ describe('The \'info\' method', () => { client.set('foo', 'bar') client.info('keyspace') return client.select(2).then(() => { - assert.strictEqual(Object.keys(client.serverInfo).length, 2, 'Key length should be three') - assert.strictEqual(typeof client.serverInfo.db0, 'object', 'db0 keyspace should be an object') + assert.strictEqual(Object.keys(client.serverInfo).length, 1, 'Key length should be one') + assert.strictEqual(Object.keys(client.serverInfo.keyspace.db0).length, 3, 'Key length should be three') + assert.strictEqual(typeof client.serverInfo.keyspace.db0, 'object', 'db0 keyspace should be an object') client.info(['keyspace']) client.set('foo', 'bar') return client.info('all').then((res) => { assert(Object.keys(client.serverInfo).length > 3, 'Key length should be way above three') - assert.strictEqual(typeof client.serverInfo.redis_version, 'string') - assert.strictEqual(typeof client.serverInfo.db2, 'object') + assert.strictEqual(typeof client.serverInfo.server.redis_version, 'string') + assert.strictEqual(typeof client.serverInfo.keyspace.db2, 'object') }) }) }) diff --git a/test/commands/select.spec.js b/test/commands/select.spec.js index da71c15963..33d559c55a 100644 --- a/test/commands/select.spec.js +++ b/test/commands/select.spec.js @@ -67,7 +67,7 @@ describe('The \'select\' method', () => { client.set('foo', 'bar').then(() => client._stream.destroy()) client.once('ready', () => { assert.strictEqual(client.selectedDb, 3) - assert(typeof client.serverInfo.db3 === 'object') + assert(typeof client.serverInfo.keyspace.db3 === 'object') done() }) }) diff --git a/test/conect.slave.spec.js b/test/conect.slave.spec.js index 63be8219b0..0f816089ad 100644 --- a/test/conect.slave.spec.js +++ b/test/conect.slave.spec.js @@ -66,8 +66,8 @@ if (process.platform !== 'win32') { const end = helper.callFuncAfter(done, 2) slave.on('ready', function () { - assert.strictEqual(this.serverInfo.master_link_status, 'up') - assert.strictEqual(firstInfo.master_link_status, 'down') + assert.strictEqual(this.serverInfo.replication.master_link_status, 'up') + assert.strictEqual(firstInfo.replication.master_link_status, 'down') assert(i > 1) this.get('foo300').then((res) => { assert.strictEqual(res.substr(0, 3), 'bar') diff --git a/test/connection.spec.js b/test/connection.spec.js index fc42963461..5c4ea7a74c 100644 --- a/test/connection.spec.js +++ b/test/connection.spec.js @@ -479,8 +479,8 @@ describe('connection tests', () => { client.info = function () { return tmp().then((res) => { if (!delayed) { - client.serverInfo.loading = 1 - client.serverInfo.loading_eta_seconds = 0.5 + client.serverInfo.persistence.loading = 1 + client.serverInfo.persistence.loading_eta_seconds = 0.5 delayed = true time = Date.now() } @@ -509,8 +509,8 @@ describe('connection tests', () => { return tmp().then((res) => { if (!delayed) { // Try reconnecting after one second even if redis tells us the time needed is above one second - client.serverInfo.loading = 1 - client.serverInfo.loading_eta_seconds = 2.5 + client.serverInfo.persistence.loading = 1 + client.serverInfo.persistence.loading_eta_seconds = 2.5 delayed = true time = Date.now() } diff --git a/test/helper.js b/test/helper.js index 330da521f7..47d2f10338 100644 --- a/test/helper.js +++ b/test/helper.js @@ -139,7 +139,7 @@ module.exports = { throw new Error('Version check not possible as the client is not yet ready or did not expose the version') } // Return true if the server version >= desiredVersion - const version = connection.serverInfo.versions + const version = connection.serverInfo.server.version for (let i = 0; i < 3; i++) { if (version[i] > desiredVersion[i]) { return true diff --git a/test/multi.spec.js b/test/multi.spec.js index d9cc2b91ad..610e7b8b59 100644 --- a/test/multi.spec.js +++ b/test/multi.spec.js @@ -15,7 +15,7 @@ describe('The \'multi\' method', () => { }) describe('regression test', () => { - it('saved buffers with charsets different than utf-8 (issue #913)', function (done) { + it('saved buffers with a charset different than utf-8 (issue #913)', function (done) { this.timeout(12000) // Windows tests on 0.10 are slow client = redis.createClient() @@ -226,13 +226,13 @@ describe('The \'multi\' method', () => { return multi.exec().then(helper.isDeepEqual([])) }) - it('runs normal calls in-between multis', () => { + it('runs normal calls in-between multi commands', () => { const multi1 = client.multi() multi1.set('m1', '123') return client.set('m2', '456') }) - it('runs simultaneous multis with the same client', () => { + it('runs simultaneous multi commands with the same client', () => { const multi1 = client.multi() multi1.set('m1', '123') multi1.get('m1') @@ -247,7 +247,7 @@ describe('The \'multi\' method', () => { ]) }) - it('runs simultaneous multis with the same client version 2', () => { + it('runs simultaneous multi commands with the same client version 2', () => { const multi2 = client.multi() const multi1 = client.multi() @@ -512,7 +512,7 @@ describe('The \'multi\' method', () => { return multi.exec().then((res) => { res[2] = res[2].substr(0, 10) assert.strictEqual(client.selectedDb, 5) - assert.deepStrictEqual(client.serverInfo.db5, { avg_ttl: 0, expires: 0, keys: 1 }) + assert.deepStrictEqual(client.serverInfo.keyspace.db5, { avg_ttl: 0, expires: 0, keys: 1 }) assert.deepStrictEqual(res, ['OK', 'OK', '# Server\r\n', 'bar']) return client.flushdb() }) diff --git a/test/node_redis.spec.js b/test/node_redis.spec.js index 517cb60ab4..bd500d1985 100644 --- a/test/node_redis.spec.js +++ b/test/node_redis.spec.js @@ -7,6 +7,7 @@ const path = require('path') const config = require('./lib/config') const helper = require('./helper') const fork = require('child_process').fork +const Errors = require('redis-errors') const redis = config.redis let client @@ -20,7 +21,7 @@ describe('The nodeRedis client', () => { const multiPrototype = data.match(/(\n| = )Multi\.prototype\.[a-z][a-zA-Z_]+/g) // Check that every entry RedisClient entry has a correspondent Multi entry assert.strictEqual(clientPrototype.filter((entry) => { - return !multiPrototype.includes(entry.replace('RedisClient', 'Multi')) + return multiPrototype.indexOf(entry.replace('RedisClient', 'Multi')) === -1 }).length, 0) assert.strictEqual(clientPrototype.length, multiPrototype.length) // Check that all entries exist only in lowercase variants @@ -82,10 +83,11 @@ describe('The nodeRedis client', () => { } } client2.on('error', (err) => { - assert.strictEqual(err.message, 'Connection forcefully ended and command aborted. It might have been processed.') + assert.strictEqual(err.message, 'Connection forcefully ended and command aborted.') assert.strictEqual(err.command, 'SELECT') - assert(err instanceof Error) - assert.strictEqual(err.name, 'AbortError') + assert(err instanceof Errors.AbortError) + assert(err instanceof Errors.InterruptError) + assert.strictEqual(err.name, 'InterruptError') }) client2.on('ready', () => { client2.end(true) @@ -178,7 +180,7 @@ describe('The nodeRedis client', () => { }) it('using multi with sendCommand should work as individual command instead of using the internal multi', () => { - // This is necessary to keep backwards compatibility and it is the only way to handle multis as you want in nodeRedis + // This is necessary to keep backwards compatibility and it is the only way to handle multi as you want in nodeRedis client.sendCommand('multi') client.sendCommand('set', ['foo', 'bar']).then(helper.isString('QUEUED')) client.get('foo') @@ -333,8 +335,8 @@ describe('The nodeRedis client', () => { const end = helper.callFuncAfter(() => { client.removeListener('connect', onConnect) client.removeListener('reconnecting', onRecon) - assert.strictEqual(client.serverInfo.db0.keys, 2) - assert.strictEqual(Object.keys(client.serverInfo.db0).length, 3) + assert.strictEqual(client.serverInfo.keyspace.db0.keys, 2) + assert.strictEqual(Object.keys(client.serverInfo.keyspace.db0).length, 3) done() }, 4) client.get('recon 1').then(helper.isString('one')).then(end) @@ -495,9 +497,9 @@ describe('The nodeRedis client', () => { }) client.once('ready', () => { client.set('foo', 'bar').then(helper.fail, (err) => { - assert.strictEqual(err.message, 'Fatal error encountered. Command aborted. It might have been processed.') + assert.strictEqual(err.message, 'Fatal error encountered. Command aborted.') assert.strictEqual(err.code, 'NR_FATAL') - assert(err instanceof redis.AbortError) + assert(err instanceof redis.InterruptError) error = err.origin }) // Make sure we call execute out of the reply diff --git a/test/prefix.spec.js b/test/prefix.spec.js index 09648946a4..a959dd1aa1 100644 --- a/test/prefix.spec.js +++ b/test/prefix.spec.js @@ -34,9 +34,9 @@ describe('prefix key names', () => { client.mset('key2', 'value2', 'key3', 'value3'), client.keys('*').then((res) => { assert.strictEqual(res.length, 3) - assert(res.includes('test:prefix:key')) - assert(res.includes('test:prefix:key2')) - assert(res.includes('test:prefix:key3')) + assert(res.indexOf('test:prefix:key') !== -1) + assert(res.indexOf('test:prefix:key2') !== -1) + assert(res.indexOf('test:prefix:key3') !== -1) }) ]) }) @@ -55,9 +55,9 @@ describe('prefix key names', () => { const prefixes = res.pop() assert.deepStrictEqual(res, ['OK', 'value', 'alue', 1, 0, 'OK']) assert.strictEqual(prefixes.length, 3) - assert(prefixes.includes('test:prefix:key')) - assert(prefixes.includes('test:prefix:key2')) - assert(prefixes.includes('test:prefix:key3')) + assert(prefixes.indexOf('test:prefix:key') !== -1) + assert(prefixes.indexOf('test:prefix:key2') !== -1) + assert(prefixes.indexOf('test:prefix:key3') !== -1) }) }) @@ -75,9 +75,9 @@ describe('prefix key names', () => { const prefixes = res.pop() assert.deepStrictEqual(res, ['OK', 'value', 'alue', 1, 0, 'OK']) assert.strictEqual(prefixes.length, 3) - assert(prefixes.includes('test:prefix:key')) - assert(prefixes.includes('test:prefix:key2')) - assert(prefixes.includes('test:prefix:key3')) + assert(prefixes.indexOf('test:prefix:key') !== -1) + assert(prefixes.indexOf('test:prefix:key2') !== -1) + assert(prefixes.indexOf('test:prefix:key3') !== -1) }) }) })