diff --git a/redisinsight/api/src/modules/browser/constants/browser-tool-commands.ts b/redisinsight/api/src/modules/browser/constants/browser-tool-commands.ts index c506704451..a3a77010cb 100644 --- a/redisinsight/api/src/modules/browser/constants/browser-tool-commands.ts +++ b/redisinsight/api/src/modules/browser/constants/browser-tool-commands.ts @@ -10,6 +10,7 @@ export enum BrowserToolKeysCommands { RenameNX = 'renamenx', MemoryUsage = 'memory usage', DbSize = 'dbsize', + InfoKeyspace = 'info keyspace', } export enum BrowserToolStringCommands { diff --git a/redisinsight/api/src/modules/browser/services/keys-business/scanner/strategies/cluster.strategy.spec.ts b/redisinsight/api/src/modules/browser/services/keys-business/scanner/strategies/cluster.strategy.spec.ts index 23afb680c0..d25b0f12c4 100644 --- a/redisinsight/api/src/modules/browser/services/keys-business/scanner/strategies/cluster.strategy.spec.ts +++ b/redisinsight/api/src/modules/browser/services/keys-business/scanner/strategies/cluster.strategy.spec.ts @@ -64,6 +64,14 @@ const mockClusterNodesEmptyResult: IGetNodeKeysResult[] = [ { ...mockNodeEmptyResult, ...mockClusterNodes[2] }, ]; +const mockRedisKeyspaceInfoResponse_1: string = '# Keyspace\r\ndb0:keys=1,expires=0,avg_ttl=0\r\n'; +const mockRedisKeyspaceInfoResponse_2: string = '# Keyspace\r\ndb0:keys=3000,expires=0,avg_ttl=0\r\n'; +const mockRedisKeyspaceInfoResponse_3: string = '# Keyspace\r\n \r\n'; +const mockRedisKeyspaceInfoResponse_4: string = '# Keyspace\r\ndb0:keys=2000,expires=0,avg_ttl=0\r\n'; +const mockRedisKeyspaceInfoResponse_5: string = '# Keyspace\r\ndb0:keys=1000,expires=0,avg_ttl=0\r\n'; +const mockRedisKeyspaceInfoResponse_6: string = '# Keyspace\r\ndb0:keys=1000000,expires=0,avg_ttl=0\r\n'; +const mockRedisKeyspaceInfoResponse_7: string = '# Keyspace\r\ndb0:keys=10,expires=0,avg_ttl=0\r\n'; + const mockGetKeysInfoFn = jest.fn(); mockGetKeysInfoFn.mockImplementation(async (clientOptions, keys) => { if (keys.length < 1) { @@ -123,11 +131,11 @@ describe('Cluster Scanner Strategy', () => { when(browserTool.execCommandFromNode) .calledWith( mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, [], expect.anything(), ) - .mockResolvedValue({ result: 1 }); + .mockResolvedValue({ result: mockRedisKeyspaceInfoResponse_1 }); strategy.getKeysInfo = jest.fn().mockResolvedValue([getKeyInfoResponse]); @@ -158,21 +166,21 @@ describe('Cluster Scanner Strategy', () => { expect(browserTool.execCommandFromNode).toHaveBeenNthCalledWith( 1, mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, [], mockClusterNodes[0], ); expect(browserTool.execCommandFromNode).toHaveBeenNthCalledWith( 2, mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, [], mockClusterNodes[1], ); expect(browserTool.execCommandFromNode).toHaveBeenNthCalledWith( 3, mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, [], mockClusterNodes[2], ); @@ -206,11 +214,11 @@ describe('Cluster Scanner Strategy', () => { when(browserTool.execCommandFromNode) .calledWith( mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, [], mockClusterNodes[0], ) - .mockResolvedValue({ result: 3000 }); + .mockResolvedValue({ result: mockRedisKeyspaceInfoResponse_2 }); when(browserTool.execCommandFromNode) .calledWith( mockClientOptions, @@ -241,11 +249,11 @@ describe('Cluster Scanner Strategy', () => { when(browserTool.execCommandFromNode) .calledWith( mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, [], mockClusterNodes[1], ) - .mockResolvedValue({ result: 2000 }); + .mockResolvedValue({ result: mockRedisKeyspaceInfoResponse_4 }); when(browserTool.execCommandFromNode) .calledWith( mockClientOptions, @@ -267,11 +275,11 @@ describe('Cluster Scanner Strategy', () => { when(browserTool.execCommandFromNode) .calledWith( mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, [], mockClusterNodes[2], ) - .mockResolvedValue({ result: 1000 }); + .mockResolvedValue({ result: mockRedisKeyspaceInfoResponse_5 }); when(browserTool.execCommandFromNode) .calledWith( mockClientOptions, @@ -311,21 +319,21 @@ describe('Cluster Scanner Strategy', () => { expect(browserTool.execCommandFromNode).toHaveBeenNthCalledWith( 1, mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, [], mockClusterNodes[0], ); expect(browserTool.execCommandFromNode).toHaveBeenNthCalledWith( 2, mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, [], mockClusterNodes[1], ); expect(browserTool.execCommandFromNode).toHaveBeenNthCalledWith( 3, mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, [], mockClusterNodes[2], ); @@ -383,11 +391,11 @@ describe('Cluster Scanner Strategy', () => { when(browserTool.execCommandFromNode) .calledWith( mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, [], mockClusterNodes[0], ) - .mockResolvedValue({ result: 3000 }); + .mockResolvedValue({ result: mockRedisKeyspaceInfoResponse_2 }); when(browserTool.execCommandFromNode) .calledWith( mockClientOptions, @@ -418,11 +426,11 @@ describe('Cluster Scanner Strategy', () => { when(browserTool.execCommandFromNode) .calledWith( mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, [], mockClusterNodes[1], ) - .mockResolvedValue({ result: 2000 }); + .mockResolvedValue({ result: mockRedisKeyspaceInfoResponse_4 }); when(browserTool.execCommandFromNode) .calledWith( mockClientOptions, @@ -444,11 +452,11 @@ describe('Cluster Scanner Strategy', () => { when(browserTool.execCommandFromNode) .calledWith( mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, [], mockClusterNodes[2], ) - .mockResolvedValue({ result: 1000000 }); + .mockResolvedValue({ result: mockRedisKeyspaceInfoResponse_6 }); when(browserTool.execCommandFromNode) .calledWith( mockClientOptions, @@ -494,21 +502,21 @@ describe('Cluster Scanner Strategy', () => { expect(browserTool.execCommandFromNode).toHaveBeenNthCalledWith( 1, mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, [], mockClusterNodes[0], ); expect(browserTool.execCommandFromNode).toHaveBeenNthCalledWith( 2, mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, [], mockClusterNodes[1], ); expect(browserTool.execCommandFromNode).toHaveBeenNthCalledWith( 3, mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, [], mockClusterNodes[2], ); @@ -590,11 +598,11 @@ describe('Cluster Scanner Strategy', () => { when(browserTool.execCommandFromNode) .calledWith( mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, [], expect.anything(), ) - .mockResolvedValue({ result: 0 }); + .mockResolvedValue({ result: mockRedisKeyspaceInfoResponse_3 }); strategy.getKeysInfo = mockGetKeysInfoFn; @@ -625,21 +633,21 @@ describe('Cluster Scanner Strategy', () => { expect(browserTool.execCommandFromNode).toHaveBeenNthCalledWith( 1, mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, [], mockClusterNodes[0], ); expect(browserTool.execCommandFromNode).toHaveBeenNthCalledWith( 2, mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, [], mockClusterNodes[1], ); expect(browserTool.execCommandFromNode).toHaveBeenNthCalledWith( 3, mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, [], mockClusterNodes[2], ); @@ -652,11 +660,11 @@ describe('Cluster Scanner Strategy', () => { when(browserTool.execCommandFromNode) .calledWith( mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, [], expect.anything(), ) - .mockResolvedValue({ result: 0 }); + .mockResolvedValue({ result: mockRedisKeyspaceInfoResponse_3 }); strategy.getKeysInfo = mockGetKeysInfoFn; @@ -687,21 +695,21 @@ describe('Cluster Scanner Strategy', () => { expect(browserTool.execCommandFromNode).toHaveBeenNthCalledWith( 1, mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, [], mockClusterNodes[0], ); expect(browserTool.execCommandFromNode).toHaveBeenNthCalledWith( 2, mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, [], mockClusterNodes[1], ); expect(browserTool.execCommandFromNode).toHaveBeenNthCalledWith( 3, mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, [], mockClusterNodes[2], ); @@ -714,11 +722,11 @@ describe('Cluster Scanner Strategy', () => { when(browserTool.execCommandFromNode) .calledWith( mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, [], expect.anything(), ) - .mockResolvedValue({ result: 0 }); + .mockResolvedValue({ result: mockRedisKeyspaceInfoResponse_3 }); strategy.getKeysInfo = mockGetKeysInfoFn; @@ -737,7 +745,7 @@ describe('Cluster Scanner Strategy', () => { expect(browserTool.execCommandFromNode).toHaveBeenNthCalledWith( 1, mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, [], mockClusterNodes[0], ); @@ -756,18 +764,18 @@ describe('Cluster Scanner Strategy', () => { ); } }); - it('should throw error on dbsize command', async () => { + it('should throw error on info keyspace command', async () => { const args = { ...getKeysDto }; const replyError: ReplyError = { ...mockRedisNoPermError, - command: 'DBSIZE', + command: 'INFO KEYSPACE', }; when(browserTool.execCommandFromNode) .calledWith( mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, expect.anything(), expect.anything(), ) @@ -785,28 +793,12 @@ describe('Cluster Scanner Strategy', () => { .fn() .mockResolvedValue([getKeyInfoResponse]); - const result = await strategy.getKeys(mockClientOptions, args); - - expect(result).toEqual([ - { - ...mockClusterNodesEmptyResult[0], - total: null, - scanned: getKeysDto.count, - keys: [getKeyInfoResponse], - }, - { - ...mockClusterNodesEmptyResult[1], - total: null, - scanned: getKeysDto.count, - keys: [getKeyInfoResponse], - }, - { - ...mockClusterNodesEmptyResult[2], - total: null, - scanned: getKeysDto.count, - keys: [getKeyInfoResponse], - }, - ]); + try { + await strategy.getKeys(mockClientOptions, args); + fail(); + } catch (err) { + expect(err.message).toEqual(replyError.message); + } }); it('should throw error on scan command', async () => { const args = { ...getKeysDto }; @@ -819,11 +811,11 @@ describe('Cluster Scanner Strategy', () => { when(browserTool.execCommandFromNode) .calledWith( mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, expect.anything(), expect.anything(), ) - .mockResolvedValue({ result: 1 }); + .mockResolvedValue({ result: mockRedisKeyspaceInfoResponse_1 }); when(browserTool.execCommandFromNode) .calledWith( mockClientOptions, @@ -846,11 +838,11 @@ describe('Cluster Scanner Strategy', () => { when(browserTool.execCommandFromNode) .calledWith( mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, [], expect.anything(), ) - .mockResolvedValue({ result: 10 }); + .mockResolvedValue({ result: mockRedisKeyspaceInfoResponse_7 }); strategy.scanNodes = jest.fn(); }); it("should call scan when math contains '?' glob", async () => { @@ -916,16 +908,15 @@ describe('Cluster Scanner Strategy', () => { }); describe('find exact key', () => { const key = getKeyInfoResponse.name; - const total = 10; beforeEach(async () => { when(browserTool.execCommandFromNode) .calledWith( mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, [], expect.anything(), ) - .mockResolvedValue({ result: total }); + .mockResolvedValue({ result: mockRedisKeyspaceInfoResponse_7 }); strategy.scanNodes = jest.fn(); strategy.getKeyInfo = jest .fn() @@ -942,19 +933,19 @@ describe('Cluster Scanner Strategy', () => { expect(result).toEqual([ { ...mockClusterNodesEmptyResult[0], - total, - scanned: total, + total: 10, + scanned: 10, keys: [getKeyInfoResponse], }, { ...mockClusterNodesEmptyResult[1], - total, - scanned: total, + total: 10, + scanned: 10, }, { ...mockClusterNodesEmptyResult[2], - total, - scanned: total, + total: 10, + scanned: 10, }, ]); expect(strategy.getKeyInfo).toHaveBeenCalledWith(clusterClient, key); @@ -972,19 +963,19 @@ describe('Cluster Scanner Strategy', () => { expect(result).toEqual([ { ...mockClusterNodesEmptyResult[0], - total, - scanned: total, + total: 10, + scanned: 10, keys: [{ ...getKeyInfoResponse, name: searchPattern }], }, { ...mockClusterNodesEmptyResult[1], - total, - scanned: total, + total: 10, + scanned: 10, }, { ...mockClusterNodesEmptyResult[2], - total, - scanned: total, + total: 10, + scanned: 10, }, ]); expect(strategy.getKeyInfo).toHaveBeenCalledWith(clusterClient, searchPattern); @@ -1005,19 +996,19 @@ describe('Cluster Scanner Strategy', () => { expect(result).toEqual([ { ...mockClusterNodesEmptyResult[0], - total, - scanned: total, + total: 10, + scanned: 10, keys: [getKeyInfoResponse], }, { ...mockClusterNodesEmptyResult[1], - total, - scanned: total, + total: 10, + scanned: 10, }, { ...mockClusterNodesEmptyResult[2], - total, - scanned: total, + total: 10, + scanned: 10, }, ]); }); @@ -1035,19 +1026,19 @@ describe('Cluster Scanner Strategy', () => { expect(result).toEqual([ { ...mockClusterNodesEmptyResult[0], - total, - scanned: total, + total: 10, + scanned: 10, keys: [], }, { ...mockClusterNodesEmptyResult[1], - total, - scanned: total, + total: 10, + scanned: 10, }, { ...mockClusterNodesEmptyResult[2], - total, - scanned: total, + total: 10, + scanned: 10, }, ]); }); @@ -1066,19 +1057,19 @@ describe('Cluster Scanner Strategy', () => { expect(result).toEqual([ { ...mockClusterNodesEmptyResult[0], - total, - scanned: total, + total: 10, + scanned: 10, keys: [], }, { ...mockClusterNodesEmptyResult[1], - total, - scanned: total, + total: 10, + scanned: 10, }, { ...mockClusterNodesEmptyResult[2], - total, - scanned: total, + total: 10, + scanned: 10, }, ]); }); diff --git a/redisinsight/api/src/modules/browser/services/keys-business/scanner/strategies/cluster.strategy.ts b/redisinsight/api/src/modules/browser/services/keys-business/scanner/strategies/cluster.strategy.ts index a4557b043d..7e8f03fc85 100644 --- a/redisinsight/api/src/modules/browser/services/keys-business/scanner/strategies/cluster.strategy.ts +++ b/redisinsight/api/src/modules/browser/services/keys-business/scanner/strategies/cluster.strategy.ts @@ -1,7 +1,7 @@ -import { toNumber, omit, isNull } from 'lodash'; +import { toNumber, omit, isNull, get } from 'lodash'; import * as isGlob from 'is-glob'; import config from 'src/utils/config'; -import { unescapeGlob } from 'src/utils'; +import { unescapeGlob, convertBulkStringsToObject } from 'src/utils'; import { BrowserToolClusterService, } from 'src/modules/browser/services/browser-tool-cluster/browser-tool-cluster.service'; @@ -40,9 +40,10 @@ export class ClusterStrategy extends AbstractStrategy { const match = args.match !== undefined ? args.match : '*'; const count = args.count || REDIS_SCAN_CONFIG.countDefault; const client = await this.redisManager.getRedisClient(clientOptions); + const currentDbIndex = get(client, ['options', 'db'], 0); const nodes = await this.getNodesToScan(clientOptions, args.cursor); const settings = await this.settingsProvider.getSettings(); - await this.calculateNodesTotalKeys(clientOptions, nodes); + await this.calculateNodesTotalKeys(clientOptions, currentDbIndex, nodes); if (!isGlob(match, { strict: false })) { const keyName = unescapeGlob(match); @@ -133,23 +134,26 @@ export class ClusterStrategy extends AbstractStrategy { private async calculateNodesTotalKeys( clientOptions, + currentDbIndex: number, nodes: IGetNodeKeysResult[], ): Promise { await Promise.all( nodes.map(async (node) => { - try { - const result = await this.redisManager.execCommandFromNode( - clientOptions, - BrowserToolKeysCommands.DbSize, - [], - { host: node.host, port: node.port }, - ); - // eslint-disable-next-line no-param-reassign - node.total = result.result; - } catch (err) { - // eslint-disable-next-line no-param-reassign - node.total = null; - } + const result = await this.redisManager.execCommandFromNode( + clientOptions, + BrowserToolKeysCommands.InfoKeyspace, + [], + { host: node.host, port: node.port }, + ) + + const info = convertBulkStringsToObject(result.result) + + if (!info[`db${currentDbIndex}`]) { + node.total = 0 + } else { + const { keys } = convertBulkStringsToObject(info[`db${currentDbIndex}`], ',', '='); + node.total = parseInt(keys, 10); + } }), ); } diff --git a/redisinsight/api/src/modules/browser/services/keys-business/scanner/strategies/standalone.strategy.spec.ts b/redisinsight/api/src/modules/browser/services/keys-business/scanner/strategies/standalone.strategy.spec.ts index 802d2e0f25..4b8134a1c1 100644 --- a/redisinsight/api/src/modules/browser/services/keys-business/scanner/strategies/standalone.strategy.spec.ts +++ b/redisinsight/api/src/modules/browser/services/keys-business/scanner/strategies/standalone.strategy.spec.ts @@ -39,6 +39,11 @@ const mockNodeEmptyResult: IGetNodeKeysResult = { keys: [], }; +const mockRedisKeyspaceInfoResponse_1: string = '# Keyspace\r\ndb0:keys=1,expires=0,avg_ttl=0\r\n'; +const mockRedisKeyspaceInfoResponse_2: string = '# Keyspace\r\ndb0:keys=1000000,expires=0,avg_ttl=0\r\n'; +const mockRedisKeyspaceInfoResponse_3: string = '# Keyspace\r\n \r\n'; +const mockRedisKeyspaceInfoResponse_4: string = '# Keyspace\r\ndb0:keys=10,expires=0,avg_ttl=0\r\n'; + let strategy; let browserTool; let settingsProvider; @@ -83,10 +88,11 @@ describe('Standalone Scanner Strategy', () => { when(browserTool.execCommand) .calledWith( mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, expect.anything(), + 'utf8', ) - .mockResolvedValue(1); + .mockResolvedValue(mockRedisKeyspaceInfoResponse_1); strategy.getKeysInfo = jest.fn().mockResolvedValue([getKeyInfoResponse]); @@ -140,10 +146,11 @@ describe('Standalone Scanner Strategy', () => { when(browserTool.execCommand) .calledWith( mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, expect.anything(), + 'utf8', ) - .mockResolvedValue(1000000); + .mockResolvedValue(mockRedisKeyspaceInfoResponse_2); strategy.getKeysInfo = jest .fn() @@ -164,8 +171,9 @@ describe('Standalone Scanner Strategy', () => { expect(browserTool.execCommand).toHaveBeenNthCalledWith( 1, mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, expect.anything(), + 'utf8', ); expect(browserTool.execCommand).toHaveBeenNthCalledWith( 2, @@ -201,10 +209,11 @@ describe('Standalone Scanner Strategy', () => { when(browserTool.execCommand) .calledWith( mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, expect.anything(), + 'utf8', ) - .mockResolvedValue(1000000); + .mockResolvedValue(mockRedisKeyspaceInfoResponse_2); strategy.getKeysInfo = jest.fn().mockResolvedValue([]); @@ -226,18 +235,20 @@ describe('Standalone Scanner Strategy', () => { expect(browserTool.execCommand).toHaveBeenNthCalledWith( 1, mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, expect.anything(), + 'utf8', ); }); it('should not call scan when total is 0', async () => { when(browserTool.execCommand) .calledWith( mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, expect.anything(), + 'utf8', ) - .mockResolvedValue(0); + .mockResolvedValue(mockRedisKeyspaceInfoResponse_3); strategy.getKeysInfo = jest.fn().mockResolvedValue([]); @@ -251,8 +262,9 @@ describe('Standalone Scanner Strategy', () => { expect(browserTool.execCommand).toBeCalledTimes(1); expect(browserTool.execCommand).toHaveBeenLastCalledWith( mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, expect.anything(), + 'utf8', ); expect(strategy.getKeysInfo).toBeCalledTimes(0); }); @@ -260,10 +272,11 @@ describe('Standalone Scanner Strategy', () => { when(browserTool.execCommand) .calledWith( mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, expect.anything(), + 'utf8', ) - .mockResolvedValue(0); + .mockResolvedValue(mockRedisKeyspaceInfoResponse_3); strategy.getKeysInfo = jest.fn().mockResolvedValue([]); strategy.scan = jest.fn().mockResolvedValue(undefined); @@ -282,44 +295,30 @@ describe('Standalone Scanner Strategy', () => { expect(result).toEqual([mockNodeEmptyResult]); expect(strategy.getKeysInfo).toBeCalledTimes(0); }); - it('should continue work when throw error on dbsize command', async () => { + it('should throw error on Info Keyspace command', async () => { const replyError: ReplyError = { ...mockRedisNoPermError, - command: 'DBSIZE', + command: 'info keyspace', }; when(browserTool.execCommand) - .calledWith(mockClientOptions, BrowserToolKeysCommands.DbSize, []) + .calledWith(mockClientOptions, BrowserToolKeysCommands.InfoKeyspace, [], 'utf8') .mockRejectedValue(replyError); - when(browserTool.execCommand) - .calledWith( - mockClientOptions, - BrowserToolKeysCommands.Scan, - expect.anything(), - null, - ) - .mockResolvedValue(['0', [getKeyInfoResponse.name]]); - - strategy.getKeysInfo = jest.fn().mockResolvedValue([getKeyInfoResponse]); - - const result = await strategy.getKeys(mockClientOptions, getKeysDto); - - expect(result).toEqual([ - { - ...mockNodeEmptyResult, - total: null, - scanned: getKeysDto.count, - keys: [getKeyInfoResponse], - }, - ]); + try { + await strategy.getKeys(mockClientOptions, getKeysDto); + fail('Should throw an error'); + } catch (err) { + expect(err.message).toEqual(replyError.message); + } }); it('should throw error on scan command', async () => { when(browserTool.execCommand) .calledWith( mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, expect.anything(), + 'utf8', ) - .mockResolvedValue(10); + .mockResolvedValue(mockRedisKeyspaceInfoResponse_1); const replyError: ReplyError = { ...mockRedisNoPermError, @@ -346,10 +345,11 @@ describe('Standalone Scanner Strategy', () => { when(browserTool.execCommand) .calledWith( mockClientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, expect.anything(), + 'utf8', ) - .mockResolvedValue(10); + .mockResolvedValue(mockRedisKeyspaceInfoResponse_4); strategy.scan = jest.fn(); }); it("should call scan when math contains '?' glob", async () => { @@ -420,10 +420,11 @@ describe('Standalone Scanner Strategy', () => { when(browserTool.execCommand) .calledWith( mockClientOptions, - BrowserToolKeysCommands.DbSize, - expect.anything(), + BrowserToolKeysCommands.InfoKeyspace, + [], + 'utf8', ) - .mockResolvedValue(total); + .mockResolvedValue(mockRedisKeyspaceInfoResponse_4); strategy.scan = jest.fn(); }); it('should find exact key when match is not glob patter', async () => { diff --git a/redisinsight/api/src/modules/browser/services/keys-business/scanner/strategies/standalone.strategy.ts b/redisinsight/api/src/modules/browser/services/keys-business/scanner/strategies/standalone.strategy.ts index 7fdb5d5ad7..8221615140 100644 --- a/redisinsight/api/src/modules/browser/services/keys-business/scanner/strategies/standalone.strategy.ts +++ b/redisinsight/api/src/modules/browser/services/keys-business/scanner/strategies/standalone.strategy.ts @@ -1,7 +1,7 @@ import * as isGlob from 'is-glob'; -import { isNull } from 'lodash'; +import { isNull, get } from 'lodash'; import config from 'src/utils/config'; -import { unescapeGlob } from 'src/utils'; +import { unescapeGlob, convertBulkStringsToObject, convertRedisInfoReplyToObject } from 'src/utils'; import { GetKeyInfoResponse, GetKeysWithDetailsResponse, @@ -36,6 +36,7 @@ export class StandaloneStrategy extends AbstractStrategy { const match = args.match !== undefined ? args.match : '*'; const count = args.count || REDIS_SCAN_CONFIG.countDefault; const client = await this.redisManager.getRedisClient(clientOptions); + const currentDbIndex = get(client, ['options', 'db'], 0); const node = { total: 0, @@ -44,14 +45,20 @@ export class StandaloneStrategy extends AbstractStrategy { cursor: parseInt(args.cursor, 10), }; - try { - node.total = await this.redisManager.execCommand( + const info = convertRedisInfoReplyToObject( + await this.redisManager.execCommand( clientOptions, - BrowserToolKeysCommands.DbSize, + BrowserToolKeysCommands.InfoKeyspace, [], - ); - } catch (e) { - node.total = null; + 'utf8', + ), + ); + const dbInfo = get(info, 'keyspace', {}); + if (!dbInfo[`db${currentDbIndex}`]) { + node.total = 0; + } else { + const { keys } = convertBulkStringsToObject(dbInfo[`db${currentDbIndex}`], ',', '='); + node.total = parseInt(keys, 10); } if (!isGlob(match, { strict: false })) { diff --git a/redisinsight/api/src/modules/bulk-actions/models/bulk-action.spec.ts b/redisinsight/api/src/modules/bulk-actions/models/bulk-action.spec.ts index cfc158181a..f2a562fb0d 100644 --- a/redisinsight/api/src/modules/bulk-actions/models/bulk-action.spec.ts +++ b/redisinsight/api/src/modules/bulk-actions/models/bulk-action.spec.ts @@ -19,6 +19,7 @@ nodeClient.sendCommand = jest.fn(); nodeClient.pipeline = jest.fn(() => ({ exec: mockExec, })); +nodeClient.options = { db: 0 }; const clusterClient = Object.create(Redis.Cluster.prototype); clusterClient.nodes = jest.fn(); @@ -42,6 +43,8 @@ const mockKeyBuffer = Buffer.from(mockKey); const mockRESPError = 'Reply Error: NOPERM for delete.'; const mockRESPErrorBuffer = Buffer.from(mockRESPError); +const mockRedisKeyspaceInfoResponse_1: string = '# Keyspace\r\ndb0:keys=10000,expires=0,avg_ttl=0\r\n'; + const generateErrors = (amount: number, raw = true): any => ( new Array(amount).fill(1) ).map( @@ -86,8 +89,8 @@ describe('AbstractBulkActionSimpleRunner', () => { describe('prepare', () => { beforeEach(() => { - nodeClient.sendCommand.mockResolvedValue(10_000); - clusterClient.sendCommand.mockResolvedValue(10_000); + nodeClient.sendCommand.mockResolvedValue(mockRedisKeyspaceInfoResponse_1); + clusterClient.sendCommand.mockResolvedValue(mockRedisKeyspaceInfoResponse_1); clusterClient.nodes = jest.fn().mockReturnValue([nodeClient, nodeClient]); }); diff --git a/redisinsight/api/src/modules/bulk-actions/models/runners/simple/abstract.bulk-action.simple.runner.spec.ts b/redisinsight/api/src/modules/bulk-actions/models/runners/simple/abstract.bulk-action.simple.runner.spec.ts index fb738064fb..350d7170a3 100644 --- a/redisinsight/api/src/modules/bulk-actions/models/runners/simple/abstract.bulk-action.simple.runner.spec.ts +++ b/redisinsight/api/src/modules/bulk-actions/models/runners/simple/abstract.bulk-action.simple.runner.spec.ts @@ -15,7 +15,7 @@ nodeClient.sendCommand = jest.fn(); nodeClient.pipeline = jest.fn(() => ({ exec: mockExec, })); - +nodeClient.options = { db: 1 }; const mockBulkActionFilter = new BulkActionFilter(); const mockCreateBulkActionDto = { @@ -35,6 +35,8 @@ const mockZeroCursorBuffer = Buffer.from('0'); const mockRESPError = 'Reply Error: NOPERM for delete.'; const mockRESPErrorBuffer = Buffer.from(mockRESPError); +const mockRedisKeyspaceInfoResponse: string = '# keyspace\r\ndb1:keys=100,expires=0,avg_ttl=0\r\n'; + describe('AbstractBulkActionSimpleRunner', () => { let deleteRunner: DeleteBulkActionSimpleRunner; @@ -54,7 +56,7 @@ describe('AbstractBulkActionSimpleRunner', () => { describe('prepareToStart', () => { it('should get total before start', async () => { - nodeClient.sendCommand.mockResolvedValueOnce(100); + nodeClient.sendCommand.mockResolvedValueOnce(mockRedisKeyspaceInfoResponse); expect(deleteRunner['progress']['total']).toEqual(0); expect(deleteRunner['progress']['scanned']).toEqual(0); diff --git a/redisinsight/api/src/modules/bulk-actions/models/runners/simple/abstract.bulk-action.simple.runner.ts b/redisinsight/api/src/modules/bulk-actions/models/runners/simple/abstract.bulk-action.simple.runner.ts index fced4157b7..843bc9c2f9 100644 --- a/redisinsight/api/src/modules/bulk-actions/models/runners/simple/abstract.bulk-action.simple.runner.ts +++ b/redisinsight/api/src/modules/bulk-actions/models/runners/simple/abstract.bulk-action.simple.runner.ts @@ -1,4 +1,6 @@ import IORedis from 'ioredis'; +import { get } from 'lodash'; +import { convertBulkStringsToObject, convertRedisInfoReplyToObject } from 'src/utils'; import { BulkActionStatus } from 'src/modules/bulk-actions/contants'; import { AbstractBulkActionRunner } from 'src/modules/bulk-actions/models/runners/abstract.bulk-action.runner'; @@ -20,9 +22,19 @@ export abstract class AbstractBulkActionSimpleRunner extends AbstractBulkActionR * @inheritDoc */ async prepareToStart() { - // @ts-ignore - const total = await this.node.sendCommand(new IORedis.Command('dbsize', [])); - this.progress.setTotal(total); + + const keyspaceInfo = convertRedisInfoReplyToObject( + // @ts-ignore + await this.node.sendCommand(new IORedis.Command('info', ['keyspace'], { replyEncoding: 'utf8' })) + ); + const dbInfo = get(keyspaceInfo, 'keyspace', {}) + if (!dbInfo[`db${this.node.options.db}`]) { + this.progress.setTotal(0); + + } else { + const { keys } = convertBulkStringsToObject(dbInfo[`db${this.node.options.db}`], ',', '='); + this.progress.setTotal(parseInt(keys, 10)); + } } /** diff --git a/redisinsight/api/src/modules/bulk-actions/providers/bulk-actions.provider.spec.ts b/redisinsight/api/src/modules/bulk-actions/providers/bulk-actions.provider.spec.ts index 70f1342410..e9871e3852 100644 --- a/redisinsight/api/src/modules/bulk-actions/providers/bulk-actions.provider.spec.ts +++ b/redisinsight/api/src/modules/bulk-actions/providers/bulk-actions.provider.spec.ts @@ -24,6 +24,7 @@ mockSocket2['emit'] = jest.fn(); const nodeClient = Object.create(Redis.prototype); nodeClient.sendCommand = jest.fn(); +nodeClient.options = { db: 0 }; const mockBulkActionFilter = Object.assign(new BulkActionFilter(), { count: 10_000,