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

feat: parse info data as numbers if possible and improve parsing

This commit is contained in:
Ruben Bridgewater
2017-05-28 00:12:36 +02:00
parent 8da9e98fe6
commit 8c63233968
14 changed files with 91 additions and 65 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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 {
obj[parts[0]] = parts[1]
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 {
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)
})
}
// Expose info key/values to users
if (obj.server && obj.server.redis_version) {
obj.server.version = obj.server.redis_version.split('.').map(Number)
}
client.serverInfo = obj
return res
}

View File

@@ -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
}

View File

@@ -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())

View File

@@ -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')
})
})
})

View File

@@ -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()
})
})

View File

@@ -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')

View File

@@ -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()
}

View File

@@ -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

View File

@@ -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()
})

View File

@@ -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

View File

@@ -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)
})
})
})