Skip to content

Commit 60c4be0

Browse files
elastic-vault-github-plugin-prod[bot]elasticmachinebartoval
authored
chore: [ES|QL] Update grammars (#121)
This PR updates the ES|QL grammars (lexer and parser) and PromQL grammars to match the latest version in Elasticsearch. --------- Co-authored-by: Grammar Sync Bot <elasticmachine@users.noreply.github.com> Co-authored-by: Valerio Bartolini <vabarjs@gmail.com>
1 parent 558ce68 commit 60c4be0

24 files changed

Lines changed: 3700 additions & 3301 deletions

src/ast/walker/__tests__/walker_promql.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,23 @@ describe('Walker PromQL support', () => {
417417
});
418418
});
419419

420+
describe('label param literal traversal', () => {
421+
test('visitPromqlLiteral is called for a named param label value via Walker', () => {
422+
const query = EsqlQuery.fromSrc('PROMQL bytes_in{job=?job}');
423+
const literals: PromQLLiteral[] = [];
424+
425+
Walker.walk(query.ast, {
426+
promql: {
427+
visitPromqlLiteral: (node) => literals.push(node),
428+
},
429+
});
430+
431+
expect(literals).toHaveLength(1);
432+
expect(literals[0].literalType).toBe('param');
433+
expect(literals[0].value).toBe('job');
434+
});
435+
});
436+
420437
describe('abort functionality with PromQL', () => {
421438
test('can abort PromQL traversal', () => {
422439
const query = EsqlQuery.fromSrc('PROMQL rate(sum(http_requests_total[5m]))');

src/embedded_languages/promql/ast/builder/builder.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import type {
1717
PromQLNumericLiteral,
1818
PromQLStringLiteral,
1919
PromQLTimeValue,
20+
PromQLParamLiteral,
21+
PromQLLabelValue,
2022
PromQLIdentifier,
2123
PromQLLabel,
2224
PromQLLabelMap,
@@ -271,6 +273,23 @@ export namespace PromQLBuilder {
271273
...PromQLBuilder.parserFields(fromParser),
272274
};
273275
};
276+
277+
export const param = (
278+
value: string | number,
279+
fromParser?: Partial<AstNodeParserFields>
280+
): PromQLParamLiteral => {
281+
const isPositional = typeof value === 'number';
282+
return {
283+
dialect: 'promql',
284+
type: 'literal',
285+
literalType: 'param',
286+
paramKind: '?',
287+
paramType: isPositional ? 'positional' : 'named',
288+
name: '',
289+
value,
290+
...PromQLBuilder.parserFields(fromParser),
291+
};
292+
};
274293
}
275294
}
276295

@@ -302,7 +321,7 @@ export namespace PromQLBuilder {
302321
export const label = (
303322
labelName: PromQLLabelName,
304323
operator: PromQLLabelMatchOperator,
305-
value: PromQLStringLiteral | undefined,
324+
value: PromQLLabelValue | undefined,
306325
fromParser?: Partial<AstNodeParserFields>
307326
): PromQLLabel => {
308327
return {

src/embedded_languages/promql/ast/walker/__tests__/promql_walker.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,21 @@ describe('Walker PromQL support', () => {
338338
});
339339
});
340340

341+
describe('label param literal traversal', () => {
342+
test('visitPromqlLiteral is called for a named param label value', () => {
343+
const query = PromQLParser.parse('bytes_in{job=?job}');
344+
const literals: PromQLLiteral[] = [];
345+
346+
PromqlWalker.walk(query.root, {
347+
visitPromqlLiteral: (node) => literals.push(node),
348+
});
349+
350+
expect(literals).toHaveLength(1);
351+
expect(literals[0].literalType).toBe('param');
352+
expect(literals[0].value).toBe('job');
353+
});
354+
});
355+
341356
describe('abort functionality with PromQL', () => {
342357
test('can abort PromQL traversal', () => {
343358
const query = PromQLParser.parse('rate(sum(http_requests_total[5m]))');

src/embedded_languages/promql/parser/__tests__/parser.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
import type {
99
PromQLBinaryExpression,
1010
PromQLFunction,
11+
PromQLLabel,
1112
PromQLLiteral,
13+
PromQLParamLiteral,
1214
PromQLParens,
1315
PromQLSelector,
1416
PromQLStringLiteral,
@@ -234,6 +236,61 @@ describe('PromQL Parser', () => {
234236
});
235237
});
236238

239+
describe('label parameter values', () => {
240+
it('parses a named label parameter (?job)', () => {
241+
const result = PromQLParser.parse('http_requests_total{job=?job}');
242+
243+
expect(result.errors).toHaveLength(0);
244+
245+
const selector = result.root.expression as PromQLSelector;
246+
expect(selector.type).toBe('selector');
247+
248+
const label = selector.labelMap?.args[0] as PromQLLabel;
249+
expect(label.incomplete).toBe(false);
250+
251+
const value = label.value as PromQLParamLiteral;
252+
expect(value.literalType).toBe('param');
253+
expect(value.paramType).toBe('named');
254+
expect(value.paramKind).toBe('?');
255+
expect(value.value).toBe('job');
256+
expect(value.text).toBe('?job');
257+
});
258+
259+
it('parses a positional label parameter (?1)', () => {
260+
const result = PromQLParser.parse('http_requests_total{job=?1}');
261+
262+
expect(result.errors).toHaveLength(0);
263+
264+
const selector = result.root.expression as PromQLSelector;
265+
const label = selector.labelMap?.args[0] as PromQLLabel;
266+
const value = label.value as PromQLParamLiteral;
267+
268+
expect(value.literalType).toBe('param');
269+
expect(value.paramType).toBe('positional');
270+
expect(value.paramKind).toBe('?');
271+
expect(value.value).toBe(1);
272+
expect(value.text).toBe('?1');
273+
});
274+
275+
it('parses a regex matcher with a named parameter (?pattern)', () => {
276+
const result = PromQLParser.parse('http_requests_total{job=~?pattern}');
277+
278+
expect(result.errors).toHaveLength(0);
279+
280+
const selector = result.root.expression as PromQLSelector;
281+
const label = selector.labelMap?.args[0] as PromQLLabel;
282+
283+
expect(label.operator).toBe('=~');
284+
285+
const value = label.value as PromQLParamLiteral;
286+
expect(value.literalType).toBe('param');
287+
expect(value.paramType).toBe('named');
288+
expect(value.paramKind).toBe('?');
289+
expect(value.value).toBe('pattern');
290+
expect(value.text).toBe('?pattern');
291+
});
292+
});
293+
237294
describe('error handling', () => {
238295
it('returns errors for invalid syntax', () => {
239296
const result = PromQLParser.parse('rate(');

src/embedded_languages/promql/parser/cst_to_ast_converter.ts

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -381,12 +381,8 @@ export class PromQLCstToAstConverter {
381381
}
382382
}
383383

384-
const stringToken = ctx.STRING();
385-
let value: ast.PromQLStringLiteral | undefined;
386-
387-
if (stringToken) {
388-
value = this.fromStringToken(stringToken.symbol);
389-
}
384+
const labelValueCtx = ctx.labelValue();
385+
const value = this.fromLabelValue(labelValueCtx);
390386

391387
const node = PromQLBuilder.label(labelName, operator, value, this.getParserFields(ctx));
392388

@@ -398,9 +394,29 @@ export class PromQLCstToAstConverter {
398394
node.incomplete = true;
399395
}
400396

397+
if (labelValueCtx?.exception) {
398+
node.incomplete = true;
399+
}
400+
401401
return node;
402402
}
403403

404+
private fromLabelValue(ctx: cst.LabelValueContext | undefined): ast.PromQLLabelValue | undefined {
405+
if (!ctx) return undefined;
406+
407+
const stringToken = ctx.STRING();
408+
if (stringToken) {
409+
return this.fromStringToken(stringToken.symbol);
410+
}
411+
412+
const paramToken = ctx.NAMED_OR_POSITIONAL_PARAM();
413+
if (paramToken) {
414+
return this.fromNamedOrPositionalParamToken(paramToken.symbol);
415+
}
416+
417+
return undefined;
418+
}
419+
404420
private fromLabelName(ctx: cst.LabelNameContext): ast.PromQLLabelName | undefined {
405421
const identCtx = ctx.identifier();
406422
if (identCtx) {
@@ -802,6 +818,16 @@ export class PromQLCstToAstConverter {
802818
);
803819
}
804820

821+
private fromNamedOrPositionalParamToken(token: antlr.Token): ast.PromQLParamLiteral {
822+
const text = token.text ?? '';
823+
const paramValue = text.slice(1);
824+
const valueAsNumber = Number(paramValue);
825+
const isPositional = String(valueAsNumber) === paramValue;
826+
const value = isPositional ? valueAsNumber : paramValue;
827+
828+
return PromQLBuilder.expression.literal.param(value, this.createParserFieldsFromToken(token));
829+
}
830+
805831
private unquoteString(text: string): string {
806832
if (text.length < 2) return text;
807833

src/embedded_languages/promql/pretty_print/__tests__/basic_pretty_printer.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ describe('parsed PromQL BasicPrettyPrinter', () => {
5858
test('multiple label matchers', () => {
5959
assertReprint('{job="api", instance=~"localhost:.*", status!="error"}');
6060
});
61+
62+
test('label with named parameter value', () => {
63+
assertReprint('http_requests_total{job=?job}');
64+
});
65+
66+
test('label with regex matcher and named parameter', () => {
67+
assertReprint('http_requests_total{job=~?pattern}');
68+
});
6169
});
6270

6371
describe('range vector selectors', () => {

src/embedded_languages/promql/pretty_print/__tests__/basic_pretty_printer_builder.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,18 @@ describe('synthetic AST (Builder-constructed)', () => {
419419
});
420420
});
421421

422+
describe('param label values', () => {
423+
test('named param as label value', () => {
424+
const metric = PromQLBuilder.identifier('http_requests_total');
425+
const labelName = PromQLBuilder.identifier('job');
426+
const labelValue = PromQLBuilder.expression.literal.param('job');
427+
const label = PromQLBuilder.label(labelName, '=', labelValue);
428+
const labelMap = PromQLBuilder.labelMap([label]);
429+
const selector = PromQLBuilder.expression.selector.node({ metric, labelMap });
430+
expect(PromQLBasicPrettyPrinter.expression(selector)).toBe('http_requests_total{job=?job}');
431+
});
432+
});
433+
422434
describe('special cases', () => {
423435
test('unknown node', () => {
424436
const unknown = PromQLBuilder.unknown();

src/embedded_languages/promql/pretty_print/basic_pretty_printer.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import type {
1919
PromQLLiteral,
2020
PromQLModifier,
2121
PromQLNumericLiteral,
22+
PromQLParamLiteral,
2223
PromQLParens,
2324
PromQLSelector,
2425
PromQLStringLiteral,
@@ -352,6 +353,8 @@ export class PromQLBasicPrettyPrinter {
352353
return this.printStringLiteral(node as PromQLStringLiteral);
353354
case 'time':
354355
return this.printTimeLiteral(node as PromQLTimeValue);
356+
case 'param':
357+
return this.printParamLiteral(node as PromQLParamLiteral);
355358
default:
356359
return String((node as PromQLNumericLiteral).value);
357360
}
@@ -408,6 +411,10 @@ export class PromQLBasicPrettyPrinter {
408411
return node.value;
409412
}
410413

414+
protected printParamLiteral(node: PromQLParamLiteral): string {
415+
return `${node.paramKind}${node.value}`;
416+
}
417+
411418
protected printIdentifier(node: PromQLIdentifier): string {
412419
return node.name;
413420
}

src/embedded_languages/promql/pretty_print/wrapping_pretty_printer.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,11 @@ export class PromQLWrappingPrettyPrinter {
445445
case 'time':
446446
doc = printer.text((node as promql.PromQLTimeValue).value);
447447
break;
448+
case 'param':
449+
doc = printer.text(
450+
`${(node as promql.PromQLParamLiteral).paramKind}${(node as promql.PromQLParamLiteral).value}`
451+
);
452+
break;
448453
default:
449454
doc = printer.text(String((node as promql.PromQLNumericLiteral).value));
450455
}

src/embedded_languages/promql/types.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* 2.0.
66
*/
77

8-
import type { ESQLLocation, EditorError, ESQLAstBaseItem } from '../../types';
8+
import type { ESQLLocation, EditorError, ESQLAstBaseItem, ESQLParamLiteral } from '../../types';
99

1010
/**
1111
* All PromQL AST nodes have a `dialect: 'promql'` property to distinguish them
@@ -131,9 +131,9 @@ export interface PromQLLabel extends PromQLAstNodeBase {
131131
operator: PromQLLabelMatchOperator;
132132

133133
/**
134-
* The label value (string literal).
134+
* The label value (string literal or named/positional parameter).
135135
*/
136-
value?: PromQLStringLiteral;
136+
value?: PromQLLabelValue;
137137
}
138138

139139
export type PromQLLabelMatchOperator = '=' | '!=' | '=~' | '!~';
@@ -359,7 +359,11 @@ export interface PromQLParens extends PromQLAstNodeBase<''> {
359359

360360
// ------------------------------------------------------------------- literals
361361

362-
export type PromQLLiteral = PromQLNumericLiteral | PromQLStringLiteral | PromQLTimeValue;
362+
export type PromQLLiteral =
363+
| PromQLNumericLiteral
364+
| PromQLStringLiteral
365+
| PromQLTimeValue
366+
| PromQLParamLiteral;
363367

364368
/**
365369
* Represents a numeric literal (integer, decimal, or hexadecimal).
@@ -380,6 +384,28 @@ export interface PromQLStringLiteral extends PromQLAstNodeBase {
380384
valueUnquoted: string;
381385
}
382386

387+
/**
388+
* Represents an ES|QL named or positional parameter used as a PromQL label value.
389+
*
390+
* ```promql
391+
* {job=?job}
392+
* {job=?1}
393+
* {job=~?pattern}
394+
* ```
395+
*/
396+
export interface PromQLParamLiteral
397+
extends PromQLAstNodeBase, ESQLParamLiteral<'named' | 'positional', '?'> {
398+
type: 'literal';
399+
literalType: 'param';
400+
paramKind: '?';
401+
paramType: 'named' | 'positional';
402+
}
403+
404+
/**
405+
* The value of a label matcher: either a string literal or a named/positional parameter.
406+
*/
407+
export type PromQLLabelValue = PromQLStringLiteral | PromQLParamLiteral;
408+
383409
/**
384410
* Represents a time/duration value.
385411
*

0 commit comments

Comments
 (0)