Skip to content

Commit a942a0e

Browse files
committed
refactor!(search): simplify PROFILE commands to return raw response
- BREAKING CHANGE: FT.PROFILE now returns raw response, letting users implement their own parsing
1 parent 0917278 commit a942a0e

File tree

6 files changed

+250
-225
lines changed

6 files changed

+250
-225
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"./packages/*"
66
],
77
"scripts": {
8+
"test-single": "TS_NODE_PROJECT='./packages/test-utils/tsconfig.json' mocha --require ts-node/register/transpile-only ",
89
"test": "npm run test -ws --if-present",
910
"build": "tsc --build",
1011
"documentation": "typedoc --out ./documentation",

packages/search/lib/commands/PROFILE_AGGREGATE.spec.ts

Lines changed: 100 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,43 +7,106 @@ import { parseArgs } from '@redis/client/lib/commands/generic-transformers';
77
import { DEFAULT_DIALECT } from '../dialect/default';
88

99
describe('PROFILE AGGREGATE', () => {
10-
describe('transformArguments', () => {
11-
it('without options', () => {
12-
assert.deepEqual(
13-
parseArgs(PROFILE_AGGREGATE, 'index', 'query'),
14-
['FT.PROFILE', 'index', 'AGGREGATE', 'QUERY', 'query', 'DIALECT', DEFAULT_DIALECT]
15-
);
16-
});
17-
18-
it('with options', () => {
19-
assert.deepEqual(
20-
parseArgs(PROFILE_AGGREGATE, 'index', 'query', {
21-
LIMITED: true,
22-
VERBATIM: true,
23-
STEPS: [{
24-
type: FT_AGGREGATE_STEPS.SORTBY,
25-
BY: '@by'
26-
}]
27-
}),
28-
['FT.PROFILE', 'index', 'AGGREGATE', 'LIMITED', 'QUERY', 'query',
29-
'VERBATIM', 'SORTBY', '1', '@by', 'DIALECT', DEFAULT_DIALECT]
30-
);
31-
});
10+
describe('transformArguments', () => {
11+
it('without options', () => {
12+
assert.deepEqual(
13+
parseArgs(PROFILE_AGGREGATE, 'index', 'query'),
14+
['FT.PROFILE', 'index', 'AGGREGATE', 'QUERY', 'query', 'DIALECT', DEFAULT_DIALECT]
15+
);
3216
});
3317

34-
testUtils.testWithClient('client.ft.search', async client => {
35-
await Promise.all([
36-
client.ft.create('index', {
37-
field: SCHEMA_FIELD_TYPE.NUMERIC
38-
}),
39-
client.hSet('1', 'field', '1'),
40-
client.hSet('2', 'field', '2')
41-
]);
42-
43-
const res = await client.ft.profileAggregate('index', '*');
44-
assert.deepEqual('None', res.profile.warning);
45-
assert.ok(typeof res.profile.iteratorsProfile.counter === 'number');
46-
assert.ok(typeof res.profile.parsingTime === 'string');
47-
assert.ok(res.results.total == 1);
48-
}, GLOBAL.SERVERS.OPEN);
18+
it('with options', () => {
19+
assert.deepEqual(
20+
parseArgs(PROFILE_AGGREGATE, 'index', 'query', {
21+
LIMITED: true,
22+
VERBATIM: true,
23+
STEPS: [{
24+
type: FT_AGGREGATE_STEPS.SORTBY,
25+
BY: '@by'
26+
}]
27+
}),
28+
['FT.PROFILE', 'index', 'AGGREGATE', 'LIMITED', 'QUERY', 'query',
29+
'VERBATIM', 'SORTBY', '1', '@by', 'DIALECT', DEFAULT_DIALECT]
30+
);
31+
});
32+
});
33+
34+
testUtils.testWithClientIfVersionWithinRange([[8], 'LATEST'], 'client.ft.search', async client => {
35+
await Promise.all([
36+
client.ft.create('index', {
37+
field: SCHEMA_FIELD_TYPE.NUMERIC
38+
}),
39+
client.hSet('1', 'field', '1'),
40+
client.hSet('2', 'field', '2')
41+
]);
42+
43+
44+
const normalizeObject = obj => JSON.parse(JSON.stringify(obj));
45+
const res = await client.ft.profileAggregate('index', '*');
46+
47+
const normalizedRes = normalizeObject(res);
48+
assert.equal(normalizedRes.results.total, 1);
49+
50+
assert.ok(normalizedRes.profile[0] === 'Shards');
51+
assert.ok(Array.isArray(normalizedRes.profile[1]));
52+
assert.ok(normalizedRes.profile[2] === 'Coordinator');
53+
assert.ok(Array.isArray(normalizedRes.profile[3]));
54+
55+
const shardProfile = normalizedRes.profile[1][0];
56+
assert.ok(shardProfile.includes('Total profile time'));
57+
assert.ok(shardProfile.includes('Parsing time'));
58+
assert.ok(shardProfile.includes('Pipeline creation time'));
59+
assert.ok(shardProfile.includes('Warning'));
60+
assert.ok(shardProfile.includes('Iterators profile'));
61+
62+
}, GLOBAL.SERVERS.OPEN);
63+
64+
testUtils.testWithClientIfVersionWithinRange([[7, 2, 0], [7, 4, 0]], 'client.ft.search', async client => {
65+
await Promise.all([
66+
client.ft.create('index', {
67+
field: SCHEMA_FIELD_TYPE.NUMERIC
68+
}),
69+
client.hSet('1', 'field', '1'),
70+
client.hSet('2', 'field', '2')
71+
]);
72+
73+
const normalizeObject = obj => JSON.parse(JSON.stringify(obj));
74+
const res = await client.ft.profileAggregate('index', '*');
75+
const normalizedRes = normalizeObject(res);
76+
assert.equal(normalizedRes.results.total, 1);
77+
78+
assert.ok(Array.isArray(normalizedRes.profile));
79+
assert.equal(normalizedRes.profile[0][0], 'Total profile time');
80+
assert.equal(normalizedRes.profile[1][0], 'Parsing time');
81+
assert.equal(normalizedRes.profile[2][0], 'Pipeline creation time');
82+
assert.equal(normalizedRes.profile[3][0], 'Warning');
83+
assert.equal(normalizedRes.profile[4][0], 'Iterators profile');
84+
assert.equal(normalizedRes.profile[5][0], 'Result processors profile');
85+
86+
const iteratorsProfile = normalizedRes.profile[4][1];
87+
assert.equal(iteratorsProfile[0], 'Type');
88+
assert.equal(iteratorsProfile[1], 'WILDCARD');
89+
assert.equal(iteratorsProfile[2], 'Time');
90+
assert.equal(iteratorsProfile[4], 'Counter');
91+
}, GLOBAL.SERVERS.OPEN);
92+
93+
testUtils.testWithClientIfVersionWithinRange([[8], 'LATEST'], '[RESP3] client.ft.search', async client => {
94+
await Promise.all([
95+
client.ft.create('index', {
96+
field: SCHEMA_FIELD_TYPE.NUMERIC
97+
}),
98+
client.hSet('1', 'field', '1'),
99+
client.hSet('2', 'field', '2')
100+
]);
101+
102+
103+
const normalizeObject = obj => JSON.parse(JSON.stringify(obj));
104+
const res = await client.ft.profileAggregate('index', '*');
105+
106+
const normalizedRes = normalizeObject(res);
107+
assert.equal(normalizedRes.Results.total_results, 1);
108+
assert.ok(normalizedRes.Profile.Shards);
109+
110+
}, GLOBAL.SERVERS.OPEN_3)
111+
49112
});
Lines changed: 29 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,35 @@
11
import { CommandParser } from '@redis/client/dist/lib/client/parser';
2-
import { Command, ReplyUnion } from "@redis/client/dist/lib/RESP/types";
3-
import AGGREGATE, { AggregateRawReply, FtAggregateOptions, parseAggregateOptions } from "./AGGREGATE";
4-
import { ProfileOptions, ProfileRawReply, ProfileReply, transformProfile } from "./PROFILE_SEARCH";
2+
import { Command, ReplyUnion, UnwrapReply } from '@redis/client/dist/lib/RESP/types';
3+
import AGGREGATE, { AggregateRawReply, FtAggregateOptions, parseAggregateOptions } from './AGGREGATE';
4+
import { ProfileOptions, ProfileRawReplyResp2, ProfileReplyResp2, } from './PROFILE_SEARCH';
55

66
export default {
77
NOT_KEYED_COMMAND: true,
8-
IS_READ_ONLY: true,
9-
parseCommand(
10-
parser: CommandParser,
11-
index: string,
12-
query: string,
13-
options?: ProfileOptions & FtAggregateOptions
14-
) {
15-
parser.push('FT.PROFILE', index, 'AGGREGATE');
16-
17-
if (options?.LIMITED) {
18-
parser.push('LIMITED');
19-
}
20-
21-
parser.push('QUERY', query);
8+
IS_READ_ONLY: true,
9+
parseCommand(
10+
parser: CommandParser,
11+
index: string,
12+
query: string,
13+
options?: ProfileOptions & FtAggregateOptions
14+
) {
15+
parser.push('FT.PROFILE', index, 'AGGREGATE');
2216

23-
parseAggregateOptions(parser, options)
24-
},
25-
transformReply: {
26-
2: (reply: ProfileAggeregateRawReply): ProfileReply => {
27-
return {
28-
results: AGGREGATE.transformReply[2](reply[0]),
29-
profile: transformProfile(reply[1])
30-
}
31-
},
32-
3: undefined as unknown as () => ReplyUnion
33-
},
34-
unstableResp3: true
35-
} as const satisfies Command;
17+
if (options?.LIMITED) {
18+
parser.push('LIMITED');
19+
}
3620

37-
type ProfileAggeregateRawReply = ProfileRawReply<AggregateRawReply>;
21+
parser.push('QUERY', query);
22+
23+
parseAggregateOptions(parser, options)
24+
},
25+
transformReply: {
26+
2: (reply: UnwrapReply<ProfileRawReplyResp2<AggregateRawReply>>): ProfileReplyResp2 => {
27+
return {
28+
results: AGGREGATE.transformReply[2](reply[0]),
29+
profile: reply[1]
30+
}
31+
},
32+
3: (reply: ReplyUnion): ReplyUnion => reply
33+
},
34+
unstableResp3: true
35+
} as const satisfies Command;

packages/search/lib/commands/PROFILE_SEARCH.spec.ts

Lines changed: 84 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,39 +6,90 @@ import { parseArgs } from '@redis/client/lib/commands/generic-transformers';
66
import { DEFAULT_DIALECT } from '../dialect/default';
77

88
describe('PROFILE SEARCH', () => {
9-
describe('transformArguments', () => {
10-
it('without options', () => {
11-
assert.deepEqual(
12-
parseArgs(PROFILE_SEARCH, 'index', 'query'),
13-
['FT.PROFILE', 'index', 'SEARCH', 'QUERY', 'query', 'DIALECT', DEFAULT_DIALECT]
14-
);
15-
});
16-
17-
it('with options', () => {
18-
assert.deepEqual(
19-
parseArgs(PROFILE_SEARCH, 'index', 'query', {
20-
LIMITED: true,
21-
VERBATIM: true,
22-
INKEYS: 'key'
23-
}),
24-
['FT.PROFILE', 'index', 'SEARCH', 'LIMITED', 'QUERY', 'query',
25-
'VERBATIM', 'INKEYS', '1', 'key', 'DIALECT', DEFAULT_DIALECT]
26-
);
27-
});
9+
describe('transformArguments', () => {
10+
it('without options', () => {
11+
assert.deepEqual(
12+
parseArgs(PROFILE_SEARCH, 'index', 'query'),
13+
['FT.PROFILE', 'index', 'SEARCH', 'QUERY', 'query', 'DIALECT', DEFAULT_DIALECT]
14+
);
2815
});
2916

30-
testUtils.testWithClient('client.ft.search', async client => {
31-
await Promise.all([
32-
client.ft.create('index', {
33-
field: SCHEMA_FIELD_TYPE.NUMERIC
34-
}),
35-
client.hSet('1', 'field', '1')
36-
]);
37-
38-
const res = await client.ft.profileSearch('index', '*');
39-
assert.strictEqual('None', res.profile.warning);
40-
assert.ok(typeof res.profile.iteratorsProfile.counter === 'number');
41-
assert.ok(typeof res.profile.parsingTime === 'string');
42-
assert.ok(res.results.total == 1);
43-
}, GLOBAL.SERVERS.OPEN);
17+
it('with options', () => {
18+
assert.deepEqual(
19+
parseArgs(PROFILE_SEARCH, 'index', 'query', {
20+
LIMITED: true,
21+
VERBATIM: true,
22+
INKEYS: 'key'
23+
}),
24+
['FT.PROFILE', 'index', 'SEARCH', 'LIMITED', 'QUERY', 'query',
25+
'VERBATIM', 'INKEYS', '1', 'key', 'DIALECT', DEFAULT_DIALECT]
26+
);
27+
});
28+
});
29+
30+
testUtils.testWithClientIfVersionWithinRange([[8], 'LATEST'], 'client.ft.search', async client => {
31+
await Promise.all([
32+
client.ft.create('index', {
33+
field: SCHEMA_FIELD_TYPE.NUMERIC
34+
}),
35+
client.hSet('1', 'field', '1')
36+
]);
37+
38+
const normalizeObject = obj => JSON.parse(JSON.stringify(obj));
39+
40+
const res = await client.ft.profileSearch('index', '*');
41+
42+
const normalizedRes = normalizeObject(res);
43+
assert.equal(normalizedRes.results.total, 1);
44+
45+
assert.ok(normalizedRes.profile[0] === 'Shards');
46+
assert.ok(Array.isArray(normalizedRes.profile[1]));
47+
assert.ok(normalizedRes.profile[2] === 'Coordinator');
48+
assert.ok(Array.isArray(normalizedRes.profile[3]));
49+
50+
const shardProfile = normalizedRes.profile[1][0];
51+
assert.ok(shardProfile.includes('Total profile time'));
52+
assert.ok(shardProfile.includes('Parsing time'));
53+
assert.ok(shardProfile.includes('Pipeline creation time'));
54+
assert.ok(shardProfile.includes('Warning'));
55+
assert.ok(shardProfile.includes('Iterators profile'));
56+
;
57+
58+
}, GLOBAL.SERVERS.OPEN);
59+
60+
61+
62+
63+
64+
testUtils.testWithClientIfVersionWithinRange([[7, 2, 0], [7, 4, 0]], 'client.ft.search', async client => {
65+
await Promise.all([
66+
client.ft.create('index', {
67+
field: SCHEMA_FIELD_TYPE.NUMERIC
68+
}),
69+
client.hSet('1', 'field', '1')
70+
]);
71+
72+
const normalizeObject = obj => JSON.parse(JSON.stringify(obj));
73+
74+
const res = await client.ft.profileSearch('index', '*');
75+
76+
const normalizedRes = normalizeObject(res);
77+
assert.equal(normalizedRes.results.total, 1);
78+
79+
assert.ok(Array.isArray(normalizedRes.profile));
80+
assert.equal(normalizedRes.profile[0][0], 'Total profile time');
81+
assert.equal(normalizedRes.profile[1][0], 'Parsing time');
82+
assert.equal(normalizedRes.profile[2][0], 'Pipeline creation time');
83+
assert.equal(normalizedRes.profile[3][0], 'Warning');
84+
assert.equal(normalizedRes.profile[4][0], 'Iterators profile');
85+
assert.equal(normalizedRes.profile[5][0], 'Result processors profile');
86+
87+
const iteratorsProfile = normalizedRes.profile[4][1];
88+
assert.equal(iteratorsProfile[0], 'Type');
89+
assert.equal(iteratorsProfile[1], 'WILDCARD');
90+
assert.equal(iteratorsProfile[2], 'Time');
91+
assert.equal(iteratorsProfile[4], 'Counter');
92+
93+
}, GLOBAL.SERVERS.OPEN);
94+
4495
});

0 commit comments

Comments
 (0)