Skip to content

Commit d647d34

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 a9333b2 commit d647d34

File tree

4 files changed

+110
-176
lines changed

4 files changed

+110
-176
lines changed

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

+29-15
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,33 @@ describe('PROFILE AGGREGATE', () => {
3131
});
3232
});
3333

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);
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+
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);
4963
});
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,38 @@
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";
5-
2+
import { Command, ReplyUnion, Resp2Reply, UnwrapReply } from '@redis/client/dist/lib/RESP/types';
3+
import AGGREGATE, { AggregateRawReply, FtAggregateOptions, parseAggregateOptions } from './AGGREGATE';
4+
import { ProfileOptions, ProfileRawReply, ProfileReply, } from './PROFILE_SEARCH';
5+
export interface TransformReplyType {
6+
2: (reply: UnwrapReply<Resp2Reply<ProfileRawReply<AggregateRawReply>>>) => ProfileReply;
7+
3: () => ReplyUnion;
8+
}
69
export default {
710
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);
11+
IS_READ_ONLY: true,
12+
parseCommand(
13+
parser: CommandParser,
14+
index: string,
15+
query: string,
16+
options?: ProfileOptions & FtAggregateOptions
17+
) {
18+
parser.push('FT.PROFILE', index, 'AGGREGATE');
2219

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;
20+
if (options?.LIMITED) {
21+
parser.push('LIMITED');
22+
}
3623

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

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

+28-14
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,32 @@ describe('PROFILE SEARCH', () => {
2727
});
2828
});
2929

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);
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 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+
}, GLOBAL.SERVERS.OPEN);
4458
});
+20-115
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
import { CommandParser } from '@redis/client/dist/lib/client/parser';
2-
import { Command, RedisArgument, ReplyUnion } from "@redis/client/dist/lib/RESP/types";
3-
import { AggregateReply } from "./AGGREGATE";
4-
import SEARCH, { FtSearchOptions, SearchRawReply, SearchReply, parseSearchOptions } from "./SEARCH";
2+
import { ArrayReply, Command, RedisArgument, ReplyUnion, Resp2Reply, TuplesReply, UnwrapReply } from '@redis/client/dist/lib/RESP/types';
3+
import { AggregateReply } from './AGGREGATE';
4+
import SEARCH, { FtSearchOptions, SearchRawReply, SearchReply, parseSearchOptions } from './SEARCH';
55

6-
export type ProfileRawReply<T> = [
7-
results: T,
8-
profile: [
9-
_: string,
10-
TotalProfileTime: string,
11-
_: string,
12-
ParsingTime: string,
13-
_: string,
14-
PipelineCreationTime: string,
15-
_: string,
16-
IteratorsProfile: Array<any>
17-
]
18-
];
6+
export type ProfileRawReply<T> = TuplesReply<[
7+
T,
8+
ArrayReply<ReplyUnion>
9+
]>;
10+
11+
export interface ProfileReply {
12+
results: SearchReply | AggregateReply;
13+
profile: ReplyUnion;
14+
}
15+
export interface TransformReplyType {
16+
2: (reply: UnwrapReply<Resp2Reply<ProfileRawReply<SearchRawReply>>>) => ProfileReply;
17+
3: () => ReplyUnion;
18+
}
1919

2020
type ProfileSearchRawReply = ProfileRawReply<SearchRawReply>;
2121

@@ -43,108 +43,13 @@ export default {
4343
parseSearchOptions(parser, options);
4444
},
4545
transformReply: {
46-
2: (reply: ProfileSearchRawReply, withoutDocuments: boolean): ProfileReply => {
46+
2: (reply: UnwrapReply<Resp2Reply<ProfileSearchRawReply>>): ProfileReply => {
4747
return {
4848
results: SEARCH.transformReply[2](reply[0]),
49-
profile: transformProfile(reply[1])
50-
}
49+
profile: reply[1]
50+
};
5151
},
5252
3: undefined as unknown as () => ReplyUnion
53-
},
53+
} as TransformReplyType,
5454
unstableResp3: true
5555
} as const satisfies Command;
56-
57-
export interface ProfileReply {
58-
results: SearchReply | AggregateReply;
59-
profile: ProfileData;
60-
}
61-
62-
interface ChildIterator {
63-
type?: string,
64-
counter?: number,
65-
term?: string,
66-
size?: number,
67-
time?: string,
68-
childIterators?: Array<ChildIterator>
69-
}
70-
71-
interface IteratorsProfile {
72-
type?: string,
73-
counter?: number,
74-
queryType?: string,
75-
time?: string,
76-
childIterators?: Array<ChildIterator>
77-
}
78-
79-
interface ProfileData {
80-
totalProfileTime: string,
81-
parsingTime: string,
82-
pipelineCreationTime: string,
83-
warning: string,
84-
iteratorsProfile: IteratorsProfile
85-
}
86-
87-
export function transformProfile(reply: Array<any>): ProfileData{
88-
return {
89-
totalProfileTime: reply[0][1],
90-
parsingTime: reply[1][1],
91-
pipelineCreationTime: reply[2][1],
92-
warning: reply[3][1] ? reply[3][1] : 'None',
93-
iteratorsProfile: transformIterators(reply[4][1])
94-
};
95-
}
96-
97-
function transformIterators(IteratorsProfile: Array<any>): IteratorsProfile {
98-
var res: IteratorsProfile = {};
99-
for (let i = 0; i < IteratorsProfile.length; i += 2) {
100-
const value = IteratorsProfile[i+1];
101-
switch (IteratorsProfile[i]) {
102-
case 'Type':
103-
res.type = value;
104-
break;
105-
case 'Counter':
106-
res.counter = value;
107-
break;
108-
case 'Time':
109-
res.time = value;
110-
break;
111-
case 'Query type':
112-
res.queryType = value;
113-
break;
114-
case 'Child iterators':
115-
res.childIterators = value.map(transformChildIterators);
116-
break;
117-
}
118-
}
119-
120-
return res;
121-
}
122-
123-
function transformChildIterators(IteratorsProfile: Array<any>): ChildIterator {
124-
var res: ChildIterator = {};
125-
for (let i = 1; i < IteratorsProfile.length; i += 2) {
126-
const value = IteratorsProfile[i+1];
127-
switch (IteratorsProfile[i]) {
128-
case 'Type':
129-
res.type = value;
130-
break;
131-
case 'Counter':
132-
res.counter = value;
133-
break;
134-
case 'Time':
135-
res.time = value;
136-
break;
137-
case 'Size':
138-
res.size = value;
139-
break;
140-
case 'Term':
141-
res.term = value;
142-
break;
143-
case 'Child iterators':
144-
res.childIterators = value.map(transformChildIterators);
145-
break;
146-
}
147-
}
148-
149-
return res;
150-
}

0 commit comments

Comments
 (0)