Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Cluster, Command, Redis } from 'ioredis';
import { isNull, toNumber, uniq } from 'lodash';
import { isUndefined, toNumber, uniq } from 'lodash';
import {
BadRequestException,
ConflictException,
Expand All @@ -26,7 +26,7 @@ import { CreateBrowserHistoryDto } from '../../dto/browser-history/create.browse

@Injectable()
export class RedisearchService {
private maxSearchResults: number | null = null;
private maxSearchResults: Map<string, null | number> = new Map();

private logger = new Logger('RedisearchService');

Expand Down Expand Up @@ -153,24 +153,26 @@ export class RedisearchService {

const client = await this.browserTool.getRedisClient(clientMetadata);

if (isNull(this.maxSearchResults)) {
if (isUndefined(this.maxSearchResults.get(clientMetadata.databaseId))) {
try {
// response: [ [ 'MAXSEARCHRESULTS', '10000' ] ]
const [[, maxSearchResults]] = await client.sendCommand(
// response: [ [ 'MAXSEARCHRESULTS', '10000' ] ]
new Command('FT.CONFIG', ['GET', 'MAXSEARCHRESULTS'], {
replyEncoding: 'utf8',
}),
) as [[string, string]];

this.maxSearchResults = toNumber(maxSearchResults);
this.maxSearchResults.set(clientMetadata.databaseId, toNumber(maxSearchResults));
} catch (error) {
this.maxSearchResults = null;
this.maxSearchResults.set(clientMetadata.databaseId, null);
}
}
// Workaround: recalculate limit to not query more then MAXSEARCHRESULTS
let safeLimit = limit;
if (this.maxSearchResults && offset + limit > this.maxSearchResults) {
safeLimit = offset <= this.maxSearchResults ? this.maxSearchResults - offset : limit;
const maxSearchResult = this.maxSearchResults.get(clientMetadata.databaseId)

if (maxSearchResult && offset + limit > maxSearchResult) {
safeLimit = offset <= maxSearchResult ? maxSearchResult - offset : limit;
}

const [total, ...keyNames] = await client.sendCommand(
Expand All @@ -193,7 +195,7 @@ export class RedisearchService {
total,
scanned: keyNames.length + offset,
keys: keyNames.map((name) => ({ name })),
maxResults: this.maxSearchResults,
maxResults: maxSearchResult,
});
} catch (e) {
this.logger.error('Failed to search keys using redisearch index', e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
validateInvalidDataTestCase,
getMainCheckFn, JoiRedisString
} from '../deps';
const { server, request, constants, rte } = deps;
const { server, request, constants, rte, localDb } = deps;

// endpoint to test
const endpoint = (instanceId = constants.TEST_INSTANCE_ID) =>
Expand Down Expand Up @@ -44,10 +44,13 @@ const mainCheckFn = getMainCheckFn(endpoint);

describe('POST /databases/:id/redisearch/search', () => {
requirements('!rte.bigData', 'rte.modules.search');
before(async () => rte.data.generateRedisearchIndexes(true));
beforeEach(() => rte.data.setRedisearchConfig('MAXSEARCHRESULTS', '10000'));
before(async () => {
await rte.data.generateRedisearchIndexes(true)
await localDb.createTestDbInstance(rte, {}, { id: constants.TEST_INSTANCE_ID_2 })
});

describe('Main', () => {
before(() => rte.data.setRedisearchConfig('MAXSEARCHRESULTS', '10000'));
describe('Validation', () => {
generateInvalidDataTestCases(dataSchema, validInputData).map(
validateInvalidDataTestCase(endpoint, dataSchema),
Expand Down Expand Up @@ -84,8 +87,14 @@ describe('POST /databases/:id/redisearch/search', () => {
expect(body.maxResults).to.gte(10000);
},
},
].map(mainCheckFn);
});

describe('maxSearchResults', () => {
[
{
name: 'Should modify limit to not exceed available search limitation',
endpoint: () => endpoint(constants.TEST_INSTANCE_ID_2),
data: {
...validInputData,
offset: 0,
Expand All @@ -98,10 +107,13 @@ describe('POST /databases/:id/redisearch/search', () => {
expect(body.total).to.eq(2000);
expect(body.maxResults).to.gte(1);
},
before: () => rte.data.setRedisearchConfig('MAXSEARCHRESULTS', '1'),
before: async () => {
await rte.data.setRedisearchConfig('MAXSEARCHRESULTS', '1')
},
},
{
name: 'Should return custom error message if MAXSEARCHRESULTS less than request.limit',
endpoint: () => endpoint(constants.TEST_INSTANCE_ID_2),
data: {
...validInputData,
offset: 10,
Expand All @@ -113,16 +125,35 @@ describe('POST /databases/:id/redisearch/search', () => {
error: 'Bad Request',
message: `Set MAXSEARCHRESULTS to at least ${numberWithSpaces(validInputData.limit)}.`,
},
before: () => rte.data.setRedisearchConfig('MAXSEARCHRESULTS', '1'),
before: async () => {
await rte.data.setRedisearchConfig('MAXSEARCHRESULTS', '1')
},
},
].map(mainCheckFn);
});

describe('ACL', () => {
requirements('rte.acl');
before(async () => rte.data.setAclUserRules('~* +@all'));
before(async () => {
await rte.data.setRedisearchConfig('MAXSEARCHRESULTS', '10000')
await rte.data.setAclUserRules('~* +@all')
});

[
{
name: 'Should return response with maxResults = null if no permissions for "ft.config" command',
endpoint: () => endpoint(constants.TEST_INSTANCE_ACL_ID),
data: validInputData,
responseSchema,
checkFn: async ({ body }) => {
expect(body.keys.length).to.eq(10);
expect(body.cursor).to.eq(10);
expect(body.scanned).to.eq(10);
expect(body.total).to.eq(2000);
expect(body.maxResults).to.eq(null);
},
before: () => rte.data.setAclUserRules('~* +@all -ft.config')
},
{
name: 'Should search',
endpoint: () => endpoint(constants.TEST_INSTANCE_ACL_ID),
Expand All @@ -143,20 +174,6 @@ describe('POST /databases/:id/redisearch/search', () => {
},
before: () => rte.data.setAclUserRules('~* +@all -ft.search')
},
{
name: 'Should return response with maxResults = null if no permissions for "ft.config" command',
endpoint: () => endpoint(constants.TEST_INSTANCE_ACL_ID),
data: validInputData,
responseSchema,
checkFn: async ({ body }) => {
expect(body.keys.length).to.eq(10);
expect(body.cursor).to.eq(10);
expect(body.scanned).to.eq(10);
expect(body.total).to.eq(2000);
expect(body.maxResults).to.eq(null);
},
before: () => rte.data.setAclUserRules('~* +@all -ft.config')
},
].map(mainCheckFn);
});
});
Expand Down
6 changes: 3 additions & 3 deletions redisinsight/api/test/helpers/local-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ const createClientCertificate = async (certificate) => {
return rep.save(certificate);
}

const createTesDbInstance = async (rte, server): Promise<void> => {
export const createTestDbInstance = async (rte, server, data: any = {}): Promise<void> => {
const rep = await getRepository(repositories.DATABASE);

const instance: any = {
Expand Down Expand Up @@ -324,7 +324,7 @@ const createTesDbInstance = async (rte, server): Promise<void> => {
passphrase: encryptData(constants.TEST_SSH_PASSPHRASE),
};
}
await rep.save(instance);
await rep.save({ ...instance, ...data});
}

export const createDatabaseInstances = async () => {
Expand Down Expand Up @@ -524,7 +524,7 @@ const truncateAll = async () => {

export const initLocalDb = async (rte, server) => {
await truncateAll();
await createTesDbInstance(rte, server);
await createTestDbInstance(rte, server);
await initAgreements();
if (rte.env.acl) {
await createAclInstance(rte, server);
Expand Down