Skip to content

Commit 6e5004a

Browse files
committed
feat: add Registered_domain ast support
1 parent 584b9d2 commit 6e5004a

8 files changed

Lines changed: 260 additions & 6 deletions

File tree

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

Lines changed: 23 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

src/ast/visitor/contexts.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import type {
1919
ESQLAstItem,
2020
ESQLAstJoinCommand,
2121
ESQLAstQueryExpression,
22+
ESQLAstRegisteredDomainCommand,
2223
ESQLAstRerankCommand,
2324
ESQLAstUriPartsCommand,
2425
ESQLColumn,
@@ -562,6 +563,12 @@ export class CompletionCommandVisitorContext<
562563
Data extends SharedData = SharedData,
563564
> extends CommandVisitorContext<Methods, Data, ESQLAstCommand> {}
564565

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+
565572
// SAMPLE <probability> [SEED <seed>]
566573
export class SampleCommandVisitorContext<
567574
Methods extends VisitorMethods = VisitorMethods,

src/ast/visitor/global_visitor_context.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type {
1313
ESQLAstHeaderCommand,
1414
ESQLAstJoinCommand,
1515
ESQLAstQueryExpression,
16+
ESQLAstRegisteredDomainCommand,
1617
ESQLAstRerankCommand,
1718
ESQLAstUriPartsCommand,
1819
ESQLColumn,
@@ -210,6 +211,14 @@ export class GlobalVisitorContext<
210211
input as any
211212
);
212213
}
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+
}
213222
case 'sample': {
214223
if (!this.methods.visitSampleCommand) break;
215224
return this.visitSampleCommand(parent, commandNode, input as any);
@@ -473,6 +482,15 @@ export class GlobalVisitorContext<
473482
return this.visitWithSpecificContext('visitCompletionCommand', context, input);
474483
}
475484

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+
476494
public visitSampleCommand(
477495
parent: contexts.VisitorContext | null,
478496
node: ESQLAstCommand,

src/ast/visitor/types.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ export type CommandVisitorInput<Methods extends VisitorMethods> = AnyToVoid<
111111
VisitorInput<Methods, 'visitJoinCommand'> &
112112
VisitorInput<Methods, 'visitRerankCommand'> &
113113
VisitorInput<Methods, 'visitChangePointCommand'> &
114-
VisitorInput<Methods, 'visitUriPartsCommand'>
114+
VisitorInput<Methods, 'visitUriPartsCommand'> &
115+
VisitorInput<Methods, 'visitRegisteredDomainCommand'>
115116
>;
116117

117118
/**
@@ -143,7 +144,8 @@ export type CommandVisitorOutput<Methods extends VisitorMethods> =
143144
| VisitorOutput<Methods, 'visitRerankCommand'>
144145
| VisitorOutput<Methods, 'visitChangePointCommand'>
145146
| VisitorOutput<Methods, 'visitCompletionCommand'>
146-
| VisitorOutput<Methods, 'visitUriPartsCommand'>;
147+
| VisitorOutput<Methods, 'visitUriPartsCommand'>
148+
| VisitorOutput<Methods, 'visitRegisteredDomainCommand'>;
147149

148150
/* eslint-disable @typescript-eslint/no-explicit-any */
149151
export interface VisitorMethods<
@@ -194,6 +196,11 @@ export interface VisitorMethods<
194196
any,
195197
any
196198
>;
199+
visitRegisteredDomainCommand?: Visitor<
200+
contexts.RegisteredDomainCommandVisitorContext<Visitors, Data>,
201+
any,
202+
any
203+
>;
197204
visitSampleCommand?: Visitor<contexts.SampleCommandVisitorContext<Visitors, Data>, any, any>;
198205
visitCommandOption?: Visitor<contexts.CommandOptionVisitorContext<Visitors, Data>, any, any>;
199206
visitFuseCommand?: Visitor<contexts.FuseCommandVisitorContext<Visitors, Data>, any, any>;
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import { EsqlQuery } from '../../composer/query';
9+
import { Walker } from '../../ast/walker';
10+
import type {
11+
ESQLAstQueryExpression,
12+
ESQLAstRegisteredDomainCommand,
13+
ESQLFunction,
14+
} from '../../types';
15+
16+
describe('REGISTERED_DOMAIN', () => {
17+
const getRegisteredDomain = (ast: ESQLAstQueryExpression): ESQLAstRegisteredDomainCommand =>
18+
Walker.match(ast, {
19+
type: 'command',
20+
name: 'registered_domain',
21+
}) as ESQLAstRegisteredDomainCommand;
22+
23+
describe('correctly formatted', () => {
24+
it('parses basic example', () => {
25+
const src = `FROM index | REGISTERED_DOMAIN parts = host`;
26+
const { ast, errors } = EsqlQuery.fromSrc(src);
27+
const registeredDomain = getRegisteredDomain(ast);
28+
29+
expect(errors.length).toBe(0);
30+
expect(registeredDomain).toMatchObject({
31+
type: 'command',
32+
name: 'registered_domain',
33+
incomplete: false,
34+
});
35+
expect(registeredDomain.targetField).toMatchObject({
36+
type: 'column',
37+
name: 'parts',
38+
});
39+
expect(registeredDomain.expression).toMatchObject({
40+
type: 'column',
41+
name: 'host',
42+
});
43+
expect(registeredDomain.args).toHaveLength(1);
44+
expect(registeredDomain.args[0]).toMatchObject({
45+
type: 'function',
46+
subtype: 'binary-expression',
47+
name: '=',
48+
});
49+
});
50+
51+
it('parses the assignment structure correctly', () => {
52+
const src = `FROM index | REGISTERED_DOMAIN parts = host`;
53+
const { ast } = EsqlQuery.fromSrc(src);
54+
const registeredDomain = getRegisteredDomain(ast);
55+
56+
const assignment = registeredDomain.args[0] as ESQLFunction;
57+
expect(assignment.args[0]).toMatchObject({
58+
type: 'column',
59+
name: 'parts',
60+
});
61+
expect(assignment.args[1]).toMatchObject({
62+
type: 'column',
63+
name: 'host',
64+
});
65+
});
66+
67+
it('parses with dotted qualified name', () => {
68+
const src = `FROM index | REGISTERED_DOMAIN my.prefix = fqdn`;
69+
const { ast, errors } = EsqlQuery.fromSrc(src);
70+
const registeredDomain = getRegisteredDomain(ast);
71+
72+
expect(errors.length).toBe(0);
73+
expect(registeredDomain.targetField).toMatchObject({
74+
type: 'column',
75+
});
76+
});
77+
78+
it('parses with function expression as source', () => {
79+
const src = `FROM index | REGISTERED_DOMAIN parts = CONCAT(host, ".com")`;
80+
const { ast, errors } = EsqlQuery.fromSrc(src);
81+
const registeredDomain = getRegisteredDomain(ast);
82+
83+
expect(errors.length).toBe(0);
84+
expect(registeredDomain.expression).toMatchObject({
85+
type: 'function',
86+
name: 'concat',
87+
});
88+
});
89+
90+
it('parses with quoted target prefix', () => {
91+
const src = 'FROM index | REGISTERED_DOMAIN `my-prefix` = fqdn';
92+
const { ast, errors } = EsqlQuery.fromSrc(src);
93+
const registeredDomain = getRegisteredDomain(ast);
94+
95+
expect(errors.length).toBe(0);
96+
expect(registeredDomain.targetField).toMatchObject({
97+
type: 'column',
98+
name: 'my-prefix',
99+
quoted: true,
100+
});
101+
});
102+
103+
it('parses with parameter expression as source', () => {
104+
const src = 'FROM index | REGISTERED_DOMAIN parts = ?fqdn_param';
105+
const { ast, errors } = EsqlQuery.fromSrc(src);
106+
const registeredDomain = getRegisteredDomain(ast);
107+
108+
expect(errors.length).toBe(0);
109+
expect(registeredDomain.expression).toMatchObject({
110+
type: 'literal',
111+
literalType: 'param',
112+
value: 'fqdn_param',
113+
});
114+
});
115+
});
116+
117+
describe('incorrectly formatted', () => {
118+
it('errors on just the command keyword', () => {
119+
const { ast, errors } = EsqlQuery.fromSrc(`FROM index | REGISTERED_DOMAIN`);
120+
const registeredDomain = getRegisteredDomain(ast);
121+
122+
expect(errors.length).toBeGreaterThan(0);
123+
expect(registeredDomain).toMatchObject({
124+
name: 'registered_domain',
125+
incomplete: true,
126+
});
127+
expect(registeredDomain.targetField).toMatchObject({
128+
type: 'column',
129+
name: '',
130+
incomplete: true,
131+
});
132+
expect(registeredDomain.expression).toBeUndefined();
133+
});
134+
135+
it('errors on missing assignment', () => {
136+
const { ast, errors } = EsqlQuery.fromSrc(`FROM index | REGISTERED_DOMAIN parts`);
137+
const registeredDomain = getRegisteredDomain(ast);
138+
139+
expect(errors.length).toBeGreaterThan(0);
140+
expect(registeredDomain).toMatchObject({
141+
name: 'registered_domain',
142+
incomplete: true,
143+
});
144+
expect(registeredDomain.targetField).toMatchObject({
145+
type: 'column',
146+
name: 'parts',
147+
});
148+
expect(registeredDomain.expression).toBeUndefined();
149+
});
150+
151+
it('errors on missing expression after assignment', () => {
152+
const { ast, errors } = EsqlQuery.fromSrc(`FROM index | REGISTERED_DOMAIN parts =`);
153+
const registeredDomain = getRegisteredDomain(ast);
154+
155+
expect(errors.length).toBeGreaterThan(0);
156+
expect(registeredDomain).toMatchObject({
157+
name: 'registered_domain',
158+
incomplete: true,
159+
});
160+
expect(registeredDomain.targetField).toMatchObject({
161+
type: 'column',
162+
name: 'parts',
163+
});
164+
expect(registeredDomain.expression).toMatchObject({
165+
type: 'unknown',
166+
incomplete: true,
167+
});
168+
});
169+
});
170+
});

src/parser/__tests__/uri_parts.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77

88
import { EsqlQuery } from '../../composer/query';
99
import { Walker } from '../../ast/walker';
10-
import type { ESQLAstUriPartsCommand, ESQLFunction } from '../../types';
10+
import type { ESQLAstQueryExpression, ESQLAstUriPartsCommand, ESQLFunction } from '../../types';
1111

1212
describe('URI_PARTS', () => {
13-
const getUriParts = (ast: ReturnType<typeof EsqlQuery.fromSrc>['ast']): ESQLAstUriPartsCommand =>
13+
const getUriParts = (ast: ESQLAstQueryExpression): ESQLAstUriPartsCommand =>
1414
Walker.match(ast, {
1515
type: 'command',
1616
name: 'uri_parts',

src/parser/core/cst_to_ast_converter.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,12 @@ export class CstToAstConverter {
477477
return this.fromCompletionCommand(completionCommandCtx);
478478
}
479479

480+
const registeredDomainCommandCtx = ctx.registeredDomainCommand();
481+
482+
if (registeredDomainCommandCtx) {
483+
return this.fromRegisteredDomainCommand(registeredDomainCommandCtx);
484+
}
485+
480486
const sampleCommandCtx = ctx.sampleCommand();
481487

482488
if (sampleCommandCtx) {
@@ -1432,6 +1438,14 @@ export class CstToAstConverter {
14321438
return command;
14331439
}
14341440

1441+
// -------------------------------------------------------- REGISTERED_DOMAIN
1442+
1443+
private fromRegisteredDomainCommand(
1444+
ctx: cst.RegisteredDomainCommandContext
1445+
): ast.ESQLAstRegisteredDomainCommand {
1446+
return this.fromQualifiedNameAssignmentCommand('registered_domain', ctx);
1447+
}
1448+
14351449
// ------------------------------------------------------------------- SAMPLE
14361450

14371451
private fromSampleCommand(ctx: cst.SampleCommandContext): ast.ESQLCommand<'sample'> {
@@ -2236,8 +2250,17 @@ export class CstToAstConverter {
22362250
// --------------------------------------------------------------- URI_PARTS
22372251

22382252
private fromUriPartsCommand(ctx: cst.UriPartsCommandContext): ast.ESQLAstUriPartsCommand {
2239-
const command = this.createCommand<'uri_parts', ast.ESQLAstUriPartsCommand>('uri_parts', ctx);
2253+
return this.fromQualifiedNameAssignmentCommand('uri_parts', ctx);
2254+
}
22402255

2256+
private fromQualifiedNameAssignmentCommand<
2257+
TName extends 'registered_domain' | 'uri_parts',
2258+
TCommand extends ast.ESQLCommand<TName> & {
2259+
targetField: ast.ESQLColumn;
2260+
expression?: ast.ESQLSingleAstItem;
2261+
},
2262+
>(name: TName, ctx: cst.RegisteredDomainCommandContext | cst.UriPartsCommandContext): TCommand {
2263+
const command = this.createCommand<TName, TCommand>(name, ctx);
22412264
const qualifiedNameCtx = ctx.qualifiedName();
22422265

22432266
if (qualifiedNameCtx && ctx.ASSIGN()) {

src/types.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ export type ESQLAstCommand =
1919
| ESQLAstCompletionCommand
2020
| ESQLAstFuseCommand
2121
| ESQLAstForkCommand
22-
| ESQLAstUriPartsCommand;
22+
| ESQLAstUriPartsCommand
23+
| ESQLAstRegisteredDomainCommand;
2324

2425
export type ESQLAstAllCommands = ESQLAstCommand | ESQLAstHeaderCommand;
2526

@@ -162,6 +163,11 @@ export interface ESQLAstUriPartsCommand extends ESQLCommand<'uri_parts'> {
162163
expression?: ESQLAstExpression;
163164
}
164165

166+
export interface ESQLAstRegisteredDomainCommand extends ESQLCommand<'registered_domain'> {
167+
targetField: ESQLColumn;
168+
expression?: ESQLAstExpression;
169+
}
170+
165171
/**
166172
* Represents a PROMQL command.
167173
*

0 commit comments

Comments
 (0)