Skip to content

Commit cd84ebd

Browse files
authored
Merge branch 'main' into universal-pretty-printer
2 parents 79aefa0 + fabf4d6 commit cd84ebd

17 files changed

Lines changed: 622 additions & 20 deletions

.github/workflows/backport.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
with:
1919
fetch-depth: 0
2020

21-
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
21+
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
2222
with:
2323
node-version-file: .nvmrc
2424

.github/workflows/release.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
with:
2222
ref: ${{ inputs.branch }}
2323

24-
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
24+
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
2525
with:
2626
node-version-file: .nvmrc
2727
cache: yarn
@@ -48,7 +48,7 @@ jobs:
4848
fetch-depth: 0
4949
persist-credentials: false
5050

51-
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
51+
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
5252
with:
5353
node-version-file: .nvmrc
5454
cache: yarn

src/ast/visitor/__tests__/commands.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,29 @@ test('can visit CHANGE_POINT command', () => {
139139
expect(list).toEqual(['CHANGE_POINT']);
140140
});
141141

142+
test('can visit REGISTERED_DOMAIN command', () => {
143+
const { ast } = EsqlQuery.fromSrc(`
144+
FROM index
145+
| REGISTERED_DOMAIN parts = host
146+
`);
147+
const visitor = new Visitor()
148+
.on('visitExpression', () => {
149+
return null;
150+
})
151+
.on('visitRegisteredDomainCommand', () => {
152+
return 'REGISTERED_DOMAIN';
153+
})
154+
.on('visitCommand', () => {
155+
return null;
156+
})
157+
.on('visitQuery', (ctx) => {
158+
return [...ctx.visitCommands()].flat();
159+
});
160+
const list = visitor.visitQuery(ast).flat().filter(Boolean);
161+
162+
expect(list).toEqual(['REGISTERED_DOMAIN']);
163+
});
164+
142165
test('can visit RERANK command', () => {
143166
const { ast } = EsqlQuery.fromSrc(`
144167
FROM movies
@@ -229,6 +252,29 @@ test('can visit COMPLETION command', () => {
229252
expect(list).toEqual(['COMPLETION']);
230253
});
231254

255+
test('can visit URI_PARTS command', () => {
256+
const { ast } = EsqlQuery.fromSrc(`
257+
FROM index
258+
| URI_PARTS parts = url
259+
`);
260+
const visitor = new Visitor()
261+
.on('visitExpression', (ctx) => {
262+
return null;
263+
})
264+
.on('visitUriPartsCommand', (ctx) => {
265+
return 'URI_PARTS';
266+
})
267+
.on('visitCommand', (ctx) => {
268+
return null;
269+
})
270+
.on('visitQuery', (ctx) => {
271+
return [...ctx.visitCommands()].flat();
272+
});
273+
const list = visitor.visitQuery(ast).flat().filter(Boolean);
274+
275+
expect(list).toEqual(['URI_PARTS']);
276+
});
277+
232278
test('can visit MMR command', () => {
233279
const { ast } = EsqlQuery.fromSrc(`
234280
FROM movies

src/ast/visitor/contexts.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ import type {
1919
ESQLAstItem,
2020
ESQLAstJoinCommand,
2121
ESQLAstQueryExpression,
22+
ESQLAstRegisteredDomainCommand,
2223
ESQLAstRerankCommand,
24+
ESQLAstUriPartsCommand,
2325
ESQLColumn,
2426
ESQLCommandOption,
2527
ESQLDecimalLiteral,
@@ -561,6 +563,12 @@ export class CompletionCommandVisitorContext<
561563
Data extends SharedData = SharedData,
562564
> extends CommandVisitorContext<Methods, Data, ESQLAstCommand> {}
563565

566+
// REGISTERED_DOMAIN <qualifiedName> = <primaryExpression>
567+
export class RegisteredDomainCommandVisitorContext<
568+
Methods extends VisitorMethods = VisitorMethods,
569+
Data extends SharedData = SharedData,
570+
> extends CommandVisitorContext<Methods, Data, ESQLAstRegisteredDomainCommand> {}
571+
564572
// SAMPLE <probability> [SEED <seed>]
565573
export class SampleCommandVisitorContext<
566574
Methods extends VisitorMethods = VisitorMethods,
@@ -579,6 +587,12 @@ export class MmrCommandVisitorContext<
579587
Data extends SharedData = SharedData,
580588
> extends CommandVisitorContext<Methods, Data, ESQLAstCommand> {}
581589

590+
// URI_PARTS <qualifiedName> = <primaryExpression>
591+
export class UriPartsCommandVisitorContext<
592+
Methods extends VisitorMethods = VisitorMethods,
593+
Data extends SharedData = SharedData,
594+
> extends CommandVisitorContext<Methods, Data, ESQLAstUriPartsCommand> {}
595+
582596
// Expressions -----------------------------------------------------------------
583597

584598
export class ExpressionVisitorContext<

src/ast/visitor/global_visitor_context.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ import type {
1313
ESQLAstHeaderCommand,
1414
ESQLAstJoinCommand,
1515
ESQLAstQueryExpression,
16+
ESQLAstRegisteredDomainCommand,
1617
ESQLAstRerankCommand,
18+
ESQLAstUriPartsCommand,
1719
ESQLColumn,
1820
ESQLFunction,
1921
ESQLIdentifier,
@@ -209,6 +211,14 @@ export class GlobalVisitorContext<
209211
input as any
210212
);
211213
}
214+
case 'registered_domain': {
215+
if (!this.methods.visitRegisteredDomainCommand) break;
216+
return this.visitRegisteredDomainCommand(
217+
parent,
218+
commandNode as ESQLAstRegisteredDomainCommand,
219+
input as any
220+
);
221+
}
212222
case 'sample': {
213223
if (!this.methods.visitSampleCommand) break;
214224
return this.visitSampleCommand(parent, commandNode, input as any);
@@ -221,6 +231,14 @@ export class GlobalVisitorContext<
221231
if (!this.methods.visitMmrCommand) break;
222232
return this.visitMmrCommand(parent, commandNode, input as any);
223233
}
234+
case 'uri_parts': {
235+
if (!this.methods.visitUriPartsCommand) break;
236+
return this.visitUriPartsCommand(
237+
parent,
238+
commandNode as ESQLAstUriPartsCommand,
239+
input as any
240+
);
241+
}
224242
}
225243
return this.visitCommandGeneric(parent, commandNode, input as any);
226244
}
@@ -464,6 +482,15 @@ export class GlobalVisitorContext<
464482
return this.visitWithSpecificContext('visitCompletionCommand', context, input);
465483
}
466484

485+
public visitRegisteredDomainCommand(
486+
parent: contexts.VisitorContext | null,
487+
node: ESQLAstRegisteredDomainCommand,
488+
input: types.VisitorInput<Methods, 'visitRegisteredDomainCommand'>
489+
): types.VisitorOutput<Methods, 'visitRegisteredDomainCommand'> {
490+
const context = new contexts.RegisteredDomainCommandVisitorContext(this, node, parent);
491+
return this.visitWithSpecificContext('visitRegisteredDomainCommand', context, input);
492+
}
493+
467494
public visitSampleCommand(
468495
parent: contexts.VisitorContext | null,
469496
node: ESQLAstCommand,
@@ -491,6 +518,15 @@ export class GlobalVisitorContext<
491518
return this.visitWithSpecificContext('visitMmrCommand', context, input);
492519
}
493520

521+
public visitUriPartsCommand(
522+
parent: contexts.VisitorContext | null,
523+
node: ESQLAstUriPartsCommand,
524+
input: types.VisitorInput<Methods, 'visitUriPartsCommand'>
525+
): types.VisitorOutput<Methods, 'visitUriPartsCommand'> {
526+
const context = new contexts.UriPartsCommandVisitorContext(this, node, parent);
527+
return this.visitWithSpecificContext('visitUriPartsCommand', context, input);
528+
}
529+
494530
// #endregion
495531

496532
// #region Expression visiting -------------------------------------------------------

src/ast/visitor/types.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,9 @@ export type CommandVisitorInput<Methods extends VisitorMethods> = AnyToVoid<
110110
VisitorInput<Methods, 'visitMvExpandCommand'> &
111111
VisitorInput<Methods, 'visitJoinCommand'> &
112112
VisitorInput<Methods, 'visitRerankCommand'> &
113-
VisitorInput<Methods, 'visitChangePointCommand'>
113+
VisitorInput<Methods, 'visitChangePointCommand'> &
114+
VisitorInput<Methods, 'visitUriPartsCommand'> &
115+
VisitorInput<Methods, 'visitRegisteredDomainCommand'>
114116
>;
115117

116118
/**
@@ -141,7 +143,9 @@ export type CommandVisitorOutput<Methods extends VisitorMethods> =
141143
| VisitorOutput<Methods, 'visitJoinCommand'>
142144
| VisitorOutput<Methods, 'visitRerankCommand'>
143145
| VisitorOutput<Methods, 'visitChangePointCommand'>
144-
| VisitorOutput<Methods, 'visitCompletionCommand'>;
146+
| VisitorOutput<Methods, 'visitCompletionCommand'>
147+
| VisitorOutput<Methods, 'visitUriPartsCommand'>
148+
| VisitorOutput<Methods, 'visitRegisteredDomainCommand'>;
145149

146150
/* eslint-disable @typescript-eslint/no-explicit-any */
147151
export interface VisitorMethods<
@@ -192,10 +196,16 @@ export interface VisitorMethods<
192196
any,
193197
any
194198
>;
199+
visitRegisteredDomainCommand?: Visitor<
200+
contexts.RegisteredDomainCommandVisitorContext<Visitors, Data>,
201+
any,
202+
any
203+
>;
195204
visitSampleCommand?: Visitor<contexts.SampleCommandVisitorContext<Visitors, Data>, any, any>;
196205
visitCommandOption?: Visitor<contexts.CommandOptionVisitorContext<Visitors, Data>, any, any>;
197206
visitFuseCommand?: Visitor<contexts.FuseCommandVisitorContext<Visitors, Data>, any, any>;
198-
visitMmrCommand?: Visitor<contexts.FuseCommandVisitorContext<Visitors, Data>, any, any>;
207+
visitMmrCommand?: Visitor<contexts.MmrCommandVisitorContext<Visitors, Data>, any, any>;
208+
visitUriPartsCommand?: Visitor<contexts.UriPartsCommandVisitorContext<Visitors, Data>, any, any>;
199209
visitExpression?: Visitor<contexts.ExpressionVisitorContext<Visitors, Data>, any, any>;
200210
visitSourceExpression?: Visitor<
201211
contexts.SourceExpressionVisitorContext<Visitors, Data>,

src/embedded_languages/promql/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,9 @@ export { PromqlWalker, type PromqlWalkerOptions } from './ast/walker';
1919
export { PromQLParser, type PromQLParseOptions } from './parser';
2020
export { PromQLErrorListener } from './parser/promql_error_listener';
2121
export { PromQLCstToAstConverter } from './parser/cst_to_ast_converter';
22+
23+
// Pretty Printer
24+
export { PromQLBasicPrettyPrinter, type PromQLBasicPrettyPrinterOptions } from './pretty_print';
25+
26+
// Type Guard
27+
export { isPromqlNode } from './ast/is';

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type {
1212
PromQLParens,
1313
PromQLSelector,
1414
PromQLStringLiteral,
15+
PromQLSubquery,
1516
PromQLUnaryExpression,
1617
} from '../../types';
1718
import { PromQLParser } from '../parser';
@@ -197,6 +198,42 @@ describe('PromQL Parser', () => {
197198
});
198199
});
199200

201+
describe('subquery', () => {
202+
it('parses a basic subquery with range and resolution', () => {
203+
const result = PromQLParser.parse('rate(http_requests_total[5m])[30m:1m]');
204+
205+
expect(result.errors).toHaveLength(0);
206+
expect(result.root.expression?.type).toBe('subquery');
207+
208+
const subquery = result.root.expression as PromQLSubquery;
209+
const expr = subquery.expr as PromQLFunction;
210+
const range = subquery.range as PromQLLiteral;
211+
const resolution = subquery.resolution as PromQLLiteral;
212+
213+
expect(expr.type).toBe('function');
214+
expect(expr.name).toBe('rate');
215+
expect(range.value).toBe('30m');
216+
expect(resolution.value).toBe('1m');
217+
});
218+
219+
it('parses a subquery with arithmetic resolution', () => {
220+
const result = PromQLParser.parse('http_requests_total[5m][30m:5m*2]');
221+
222+
expect(result.errors).toHaveLength(0);
223+
expect(result.root.expression?.type).toBe('subquery');
224+
225+
const subquery = result.root.expression as PromQLSubquery;
226+
const resolution = subquery.resolution as PromQLBinaryExpression;
227+
const left = resolution.left as PromQLLiteral;
228+
const right = resolution.right as PromQLLiteral;
229+
230+
expect(resolution.type).toBe('binary-expression');
231+
expect(resolution.name).toBe('*');
232+
expect(left.value).toBe('5m');
233+
expect(right.value).toBe(2);
234+
});
235+
});
236+
200237
describe('error handling', () => {
201238
it('returns errors for invalid syntax', () => {
202239
const result = PromQLParser.parse('rate(');

src/embedded_languages/promql/parser/cst_to_ast_converter.ts

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -498,11 +498,11 @@ export class PromQLCstToAstConverter {
498498
const groupLabelsCtx = ctx._groupLabels;
499499
const groupLabels = groupLabelsCtx ? this.fromLabelList(groupLabelsCtx) : [];
500500

501-
groupModifier = PromQLBuilder.groupModifier(joiningKind, groupLabels, {
502-
text: joiningToken.text ?? '',
503-
location: getPosition(joiningToken, joiningToken),
504-
incomplete: false,
505-
});
501+
groupModifier = PromQLBuilder.groupModifier(
502+
joiningKind,
503+
groupLabels,
504+
this.createParserFieldsFromToken(joiningToken, joiningToken.text ?? '')
505+
);
506506

507507
for (const label of groupLabels) {
508508
if (label.incomplete) {
@@ -574,7 +574,7 @@ export class PromQLCstToAstConverter {
574574
if (resDurationCtx) {
575575
resolution = this.fromDuration(resDurationCtx);
576576
} else {
577-
// Check for TIME_VALUE_WITH_COLON (e.g., ":5m")
577+
// Check for TIME_VALUE_WITH_COLON (e.g., ":5m", ":5m*2", ":5m^2")
578578
const timeValueWithColon = resolutionCtx.TIME_VALUE_WITH_COLON();
579579
if (timeValueWithColon) {
580580
// TIME_VALUE_WITH_COLON includes the leading colon, e.g., ":5m"
@@ -585,6 +585,28 @@ export class PromQLCstToAstConverter {
585585
timeValue,
586586
this.createParserFieldsFromToken(timeValueWithColon.symbol, timeValue)
587587
);
588+
589+
const opToken = resolutionCtx._op;
590+
const rightCtx = resolutionCtx.expression();
591+
592+
if (opToken && rightCtx) {
593+
const operator = this.toBinaryOperator(opToken.text ?? '');
594+
const right =
595+
this.fromExpression(rightCtx) ??
596+
PromQLBuilder.unknown(this.getParserFields(rightCtx));
597+
598+
resolution = PromQLBuilder.expression.binary(
599+
operator,
600+
resolution,
601+
right,
602+
{},
603+
this.getParserFields(resolutionCtx)
604+
);
605+
606+
if (right.incomplete) {
607+
resolution.incomplete = true;
608+
}
609+
}
588610
}
589611
}
590612
}

0 commit comments

Comments
 (0)