Skip to content

Commit d11d911

Browse files
committed
improve promql params map parsing
1 parent 235c094 commit d11d911

5 files changed

Lines changed: 431 additions & 54 deletions

File tree

src/platform/packages/shared/kbn-esql-language/src/ast/builder/builder.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,21 @@ export namespace Builder {
375375
name: '',
376376
};
377377
};
378+
379+
export const bare = (
380+
template: Omit<AstNodeTemplate<ESQLList>, 'name' | 'values'> &
381+
Partial<Pick<ESQLList, 'values'>> = {},
382+
fromParser?: Partial<AstNodeParserFields>
383+
): ESQLList => {
384+
return {
385+
values: [],
386+
...template,
387+
...Builder.parserFields(fromParser),
388+
type: 'list',
389+
subtype: 'bare',
390+
name: '',
391+
};
392+
};
378393
}
379394

380395
export namespace literal {

src/platform/packages/shared/kbn-esql-language/src/parser/__tests__/promql.test.ts

Lines changed: 267 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -75,58 +75,282 @@ describe('PROMQL command', () => {
7575
expect(paramsMap.entries.length).toBe(3);
7676
});
7777

78-
describe('<params> map with assignment syntax', () => {
79-
it('parses param name=value pairs as map entries', () => {
80-
const text = `PROMQL start="2024-01-01" (up)`;
81-
const query = EsqlQuery.fromSrc(text);
82-
const promqlCmd = query.ast.commands[0] as ESQLCommand<'promql'>;
83-
84-
const paramsMap = promqlCmd.args[0] as ESQLMap;
85-
expect(paramsMap.type).toBe('map');
86-
expect(paramsMap.representation).toBe('assignment');
78+
describe('PROMQL <params>', () => {
79+
describe('index patterns', () => {
80+
describe('indexString only', () => {
81+
describe('UNQUOTED_IDENTIFIER', () => {
82+
it('single index as UNQUOTED_IDENTIFIER', () => {
83+
const text = 'PROMQL a = abc query';
84+
const query = EsqlQuery.fromSrc(text);
85+
const promqlCmd = query.ast.commands[0] as ESQLCommand<'promql'>;
86+
const paramsMap = promqlCmd.args[0] as ESQLMap;
87+
88+
expect('\n' + printAst(paramsMap)).toBe(`
89+
map 7-13
90+
└─ map-entry 7-13
91+
├─ identifier 7-7 "a"
92+
└─ list 11-13
93+
└─ source 11-13 "abc"
94+
└─ literal 11-13 ""abc""`);
95+
});
96+
97+
it('single index as UNQUOTED_IDENTIFIER (starts with underscore)', () => {
98+
const text = 'PROMQL a = _abc query';
99+
const query = EsqlQuery.fromSrc(text);
100+
const promqlCmd = query.ast.commands[0] as ESQLCommand<'promql'>;
101+
const paramsMap = promqlCmd.args[0] as ESQLMap;
102+
103+
expect('\n' + printAst(paramsMap)).toBe(`
104+
map 7-14
105+
└─ map-entry 7-14
106+
├─ identifier 7-7 "a"
107+
└─ list 11-14
108+
└─ source 11-14 "_abc"
109+
└─ literal 11-14 ""_abc""`);
110+
});
111+
112+
it('single index as UNQUOTED_IDENTIFIER (starts with ampersand)', () => {
113+
const text = 'PROMQL a = &abc query';
114+
const query = EsqlQuery.fromSrc(text);
115+
const promqlCmd = query.ast.commands[0] as ESQLCommand<'promql'>;
116+
const paramsMap = promqlCmd.args[0] as ESQLMap;
117+
118+
expect('\n' + printAst(paramsMap)).toBe(`
119+
map 7-14
120+
└─ map-entry 7-14
121+
├─ identifier 7-7 "a"
122+
└─ list 11-14
123+
└─ source 11-14 "&abc"
124+
└─ literal 11-14 ""&abc""`);
125+
});
126+
127+
it('single param multiple index patterns as UNQUOTED_IDENTIFIER', () => {
128+
const text = 'PROMQL a = a, b, c query';
129+
const query = EsqlQuery.fromSrc(text);
130+
const promqlCmd = query.ast.commands[0] as ESQLCommand<'promql'>;
131+
const paramsMap = promqlCmd.args[0] as ESQLMap;
132+
133+
expect('\n' + printAst(paramsMap)).toBe(`
134+
map 7-17
135+
└─ map-entry 7-17
136+
├─ identifier 7-7 "a"
137+
└─ list 11-17
138+
├─ source 11-11 "a"
139+
│ └─ literal 11-11 ""a""
140+
├─ source 14-14 "b"
141+
│ └─ literal 14-14 ""b""
142+
└─ source 17-17 "c"
143+
└─ literal 17-17 ""c""`);
144+
});
145+
146+
it('single param multiple index patterns of different types', () => {
147+
const text = 'PROMQL a = a, _b, &c query';
148+
const query = EsqlQuery.fromSrc(text);
149+
const promqlCmd = query.ast.commands[0] as ESQLCommand<'promql'>;
150+
const paramsMap = promqlCmd.args[0] as ESQLMap;
151+
152+
expect('\n' + printAst(paramsMap)).toBe(`
153+
map 7-19
154+
└─ map-entry 7-19
155+
├─ identifier 7-7 "a"
156+
└─ list 11-19
157+
├─ source 11-11 "a"
158+
│ └─ literal 11-11 ""a""
159+
├─ source 14-15 "_b"
160+
│ └─ literal 14-15 ""_b""
161+
└─ source 18-19 "&c"
162+
└─ literal 18-19 ""&c""`);
163+
});
164+
});
165+
166+
describe('UNQUOTED_SOURCE', () => {
167+
it('single index as UNQUOTED_SOURCE', () => {
168+
const text = 'PROMQL a = /xy query';
169+
const query = EsqlQuery.fromSrc(text);
170+
const promqlCmd = query.ast.commands[0] as ESQLCommand<'promql'>;
171+
const paramsMap = promqlCmd.args[0] as ESQLMap;
172+
173+
expect('\n' + printAst(paramsMap)).toBe(`
174+
map 7-13
175+
└─ map-entry 7-13
176+
├─ identifier 7-7 "a"
177+
└─ list 11-13
178+
└─ source 11-13 "/xy"
179+
└─ literal 11-13 ""/xy""`);
180+
});
181+
182+
it('two UNQUOTED_SOURCEs', () => {
183+
const text = 'PROMQL indices = /a, /b query';
184+
const query = EsqlQuery.fromSrc(text);
185+
const promqlCmd = query.ast.commands[0] as ESQLCommand<'promql'>;
186+
const paramsMap = promqlCmd.args[0] as ESQLMap;
187+
188+
expect('\n' + printAst(paramsMap)).toBe(`
189+
map 7-22
190+
└─ map-entry 7-22
191+
├─ identifier 7-13 "indices"
192+
└─ list 17-22
193+
├─ source 17-18 "/a"
194+
│ └─ literal 17-18 ""/a""
195+
└─ source 21-22 "/b"
196+
└─ literal 21-22 ""/b""`);
197+
});
198+
});
199+
200+
describe('QUOTED_STRING', () => {
201+
it('single index as QUOTED_STRING', () => {
202+
const text = 'PROMQL a = "str" query';
203+
const query = EsqlQuery.fromSrc(text);
204+
const promqlCmd = query.ast.commands[0] as ESQLCommand<'promql'>;
205+
const paramsMap = promqlCmd.args[0] as ESQLMap;
206+
207+
expect('\n' + printAst(paramsMap)).toBe(`
208+
map 7-15
209+
└─ map-entry 7-15
210+
├─ identifier 7-7 "a"
211+
└─ list 11-15
212+
└─ source 11-15 ""str""
213+
└─ literal 11-15 ""str""`);
214+
});
215+
216+
it('two x two indices as QUOTED_STRINGs', () => {
217+
const text = 'PROMQL a = "str", "str2" b = "str3", "str4" query';
218+
const query = EsqlQuery.fromSrc(text);
219+
const promqlCmd = query.ast.commands[0] as ESQLCommand<'promql'>;
220+
const paramsMap = promqlCmd.args[0] as ESQLMap;
221+
222+
expect('\n' + printAst(paramsMap)).toBe(`
223+
map 7-42
224+
├─ map-entry 7-23
225+
│ ├─ identifier 7-7 "a"
226+
│ └─ list 11-23
227+
│ ├─ source 11-15 ""str""
228+
│ │ └─ literal 11-15 ""str""
229+
│ └─ source 18-23 ""str2""
230+
│ └─ literal 18-23 ""str2""
231+
└─ map-entry 25-42
232+
├─ identifier 25-25 "b"
233+
└─ list 29-42
234+
├─ source 29-34 ""str3""
235+
│ └─ literal 29-34 ""str3""
236+
└─ source 37-42 ""str4""
237+
└─ literal 37-42 ""str4""`);
238+
});
239+
});
240+
});
87241

88-
const startEntry = paramsMap.entries.find(
89-
(entry): entry is ESQLMapEntry =>
90-
entry.key.type === 'identifier' && entry.key.name === 'start'
91-
);
242+
describe('clusterString + indexString', () => {
243+
it('single index as UNQUOTED_IDENTIFIER', () => {
244+
const text = 'PROMQL name = cluster:index query';
245+
const query = EsqlQuery.fromSrc(text);
246+
const promqlCmd = query.ast.commands[0] as ESQLCommand<'promql'>;
247+
const paramsMap = promqlCmd.args[0] as ESQLMap;
248+
249+
expect('\n' + printAst(paramsMap)).toBe(`
250+
map 7-26
251+
└─ map-entry 7-26
252+
├─ identifier 7-10 "name"
253+
└─ list 14-26
254+
└─ source 14-26 "cluster:index"
255+
├─ literal 14-20 ""cluster""
256+
└─ literal 22-26 ""index""`);
257+
});
258+
259+
it('single index as UNQUOTED_SOURCE', () => {
260+
const text = 'PROMQL name = /a:/b query';
261+
const query = EsqlQuery.fromSrc(text);
262+
const promqlCmd = query.ast.commands[0] as ESQLCommand<'promql'>;
263+
const paramsMap = promqlCmd.args[0] as ESQLMap;
264+
265+
expect('\n' + printAst(paramsMap)).toBe(`
266+
map 7-18
267+
└─ map-entry 7-18
268+
├─ identifier 7-10 "name"
269+
└─ list 14-18
270+
└─ source 14-18 "/a:/b"
271+
├─ literal 14-15 ""/a""
272+
└─ literal 17-18 ""/b""`);
273+
});
274+
});
92275

93-
expect(startEntry).toBeDefined();
94-
expect(startEntry?.value).toMatchObject({
95-
type: 'identifier',
96-
name: '"2024-01-01"',
276+
describe('indexString + selectorString', () => {
277+
it('single index as UNQUOTED_IDENTIFIER', () => {
278+
const text = 'PROMQL name = index::selector query';
279+
const query = EsqlQuery.fromSrc(text);
280+
const promqlCmd = query.ast.commands[0] as ESQLCommand<'promql'>;
281+
const paramsMap = promqlCmd.args[0] as ESQLMap;
282+
283+
expect('\n' + printAst(paramsMap)).toBe(`
284+
map 7-28
285+
└─ map-entry 7-28
286+
├─ identifier 7-10 "name"
287+
└─ list 14-28
288+
└─ source 14-28 "index::selector"
289+
├─ literal 14-18 ""index""
290+
└─ literal 21-28 ""selector""`);
291+
});
292+
293+
it('single index as UNQUOTED_SOURCE', () => {
294+
const text = 'PROMQL name = /index::/selector query';
295+
const query = EsqlQuery.fromSrc(text);
296+
const promqlCmd = query.ast.commands[0] as ESQLCommand<'promql'>;
297+
const paramsMap = promqlCmd.args[0] as ESQLMap;
298+
299+
expect('\n' + printAst(paramsMap)).toBe(`
300+
map 7-30
301+
└─ map-entry 7-30
302+
├─ identifier 7-10 "name"
303+
└─ list 14-30
304+
└─ source 14-30 "/index::/selector"
305+
├─ literal 14-19 ""/index""
306+
└─ literal 22-30 ""/selector""`);
307+
});
97308
});
98309
});
99310

100-
it('parses unquoted identifier param values', () => {
101-
const text = `PROMQL index=k8s (up)`;
102-
const query = EsqlQuery.fromSrc(text);
103-
const promqlCmd = query.ast.commands[0] as ESQLCommand<'promql'>;
104-
105-
const paramsMap = promqlCmd.args[0] as ESQLMap;
106-
const indexEntry = paramsMap.entries.find(
107-
(entry): entry is ESQLMapEntry =>
108-
entry.key.type === 'identifier' && entry.key.name === 'index'
109-
);
110-
111-
expect(indexEntry).toBeDefined();
112-
expect(indexEntry?.value).toMatchObject({
113-
type: 'identifier',
114-
name: 'k8s',
311+
describe('identifiers', () => {
312+
it('single index as QUOTED_IDENTIFIER', () => {
313+
const text = 'PROMQL a = `ab.c` query';
314+
const query = EsqlQuery.fromSrc(text);
315+
const promqlCmd = query.ast.commands[0] as ESQLCommand<'promql'>;
316+
const paramsMap = promqlCmd.args[0] as ESQLMap;
317+
318+
expect('\n' + printAst(paramsMap)).toBe(`
319+
map 7-16
320+
└─ map-entry 7-16
321+
├─ identifier 7-7 "a"
322+
└─ identifier 11-16 "ab.c"`);
115323
});
116324
});
117325

118-
it('parses PROMQL with quoted identifier param names', () => {
119-
const text = 'PROMQL `start-time`="2024-01-01" (up)';
120-
const query = EsqlQuery.fromSrc(text);
121-
const promqlCmd = query.ast.commands[0] as ESQLCommand<'promql'>;
122-
123-
const paramsMap = promqlCmd.args[0] as ESQLMap;
124-
const entry = paramsMap.entries[0];
326+
describe('params', () => {
327+
it('single named param', () => {
328+
const text = 'PROMQL a = ?name query';
329+
const query = EsqlQuery.fromSrc(text);
330+
const promqlCmd = query.ast.commands[0] as ESQLCommand<'promql'>;
331+
const paramsMap = promqlCmd.args[0] as ESQLMap;
332+
333+
expect('\n' + printAst(paramsMap)).toBe(`
334+
map 7-15
335+
└─ map-entry 7-15
336+
├─ identifier 7-7 "a"
337+
└─ literal 11-15 ?name`);
338+
});
125339

126-
expect(entry).toBeDefined();
127-
expect(entry.key).toMatchObject({
128-
type: 'identifier',
129-
name: 'start-time',
340+
it('two positional params', () => {
341+
const text = 'PROMQL a = ?0 b = ?1 query';
342+
const query = EsqlQuery.fromSrc(text);
343+
const promqlCmd = query.ast.commands[0] as ESQLCommand<'promql'>;
344+
const paramsMap = promqlCmd.args[0] as ESQLMap;
345+
346+
expect('\n' + printAst(paramsMap)).toBe(`
347+
map 7-19
348+
├─ map-entry 7-12
349+
│ ├─ identifier 7-7 "a"
350+
│ └─ literal 11-12 ?0
351+
└─ map-entry 14-19
352+
├─ identifier 14-14 "b"
353+
└─ literal 18-19 ?1`);
130354
});
131355
});
132356
});

0 commit comments

Comments
 (0)