Skip to content

Commit 30ca4be

Browse files
authored
[ES|QL] Support subqueries in pretty printer (#241473)
## Summary support subqueries for pretty printer
1 parent ce4527c commit 30ca4be

File tree

9 files changed

+148
-10
lines changed

9 files changed

+148
-10
lines changed

src/platform/packages/shared/kbn-esql-ast/src/pretty_print/__tests__/basic_pretty_printer.comments.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,3 +285,12 @@ describe('commands', () => {
285285
});
286286
});
287287
});
288+
289+
describe('subqueries (parens)', () => {
290+
test('can print comments in complex subqueries', () => {
291+
const query =
292+
'FROM index1, /* before subquery */ (/* inside start */ FROM index2 /* after source */ | WHERE a > 10 /* after where */ | EVAL b = a * 2 | STATS cnt = COUNT(*) BY c | SORT cnt DESC | LIMIT 10) /* after first subquery */, index3, (FROM index4 | STATS COUNT(*)) /* after second */ | WHERE d > 10 | STATS max = MAX(*) BY e | SORT max DESC';
293+
294+
assertPrint(query);
295+
});
296+
});

src/platform/packages/shared/kbn-esql-ast/src/pretty_print/__tests__/basic_pretty_printer.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1098,3 +1098,14 @@ describe('unary operator precedence and grouping', () => {
10981098
assertReprint('ROW (a + b) * (c + d)');
10991099
});
11001100
});
1101+
1102+
describe('subqueries (parens)', () => {
1103+
test('can print complex subqueries with processing', () => {
1104+
const src =
1105+
'FROM index1, (FROM index2 | WHERE a > 10 | EVAL b = a * 2 | STATS cnt = COUNT(*) BY c | SORT cnt DESC | LIMIT 10), index3, (FROM index4 | STATS count(*)) | WHERE d > 10 | STATS max = max(*) BY e | SORT max DESC';
1106+
const expected =
1107+
'FROM index1, (FROM index2 | WHERE a > 10 | EVAL b = a * 2 | STATS cnt = COUNT(*) BY c | SORT cnt DESC | LIMIT 10), index3, (FROM index4 | STATS COUNT(*)) | WHERE d > 10 | STATS max = MAX(*) BY e | SORT max DESC';
1108+
1109+
assertReprint(src, expected);
1110+
});
1111+
});

src/platform/packages/shared/kbn-esql-ast/src/pretty_print/__tests__/wrapping_pretty_printer.comments.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -932,4 +932,42 @@ FROM index`;
932932
});
933933
});
934934
});
935+
936+
describe('subqueries (parens)', () => {
937+
test('can print comments in complex subqueries', () => {
938+
// This single test covers comment preservation in wrapped output
939+
const query = [
940+
'FROM index1,',
941+
'/* before subquery */ (/* inside start */ FROM index2 /* after source */ |',
942+
'WHERE a > 10 /* after where */ |',
943+
'EVAL b = a * 2 |',
944+
'STATS cnt = COUNT(*) BY c |',
945+
'SORT cnt DESC |',
946+
'LIMIT 10) /* after first subquery */,',
947+
'index3,',
948+
'(FROM index4 | STATS COUNT(*)) /* after second */ |',
949+
'WHERE d > 10 |',
950+
'STATS max = MAX(*) BY e |',
951+
'SORT max DESC',
952+
].join(' ');
953+
954+
const expected = `FROM
955+
index1,
956+
/* before subquery */ (/* inside start */ FROM index2 /* after source */
957+
| WHERE a > 10 /* after where */
958+
| EVAL b = a * 2
959+
| STATS cnt = COUNT(*)
960+
BY c
961+
| SORT cnt DESC
962+
| LIMIT 10) /* after first subquery */,
963+
index3,
964+
(FROM index4 | STATS COUNT(*)) /* after second */
965+
| WHERE d > 10
966+
| STATS max = MAX(*)
967+
BY e
968+
| SORT max DESC`;
969+
970+
assertReprint(query, expected);
971+
});
972+
});
935973
});

src/platform/packages/shared/kbn-esql-ast/src/pretty_print/__tests__/wrapping_pretty_printer.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1310,4 +1310,30 @@ describe('unary operator precedence and grouping', () => {
13101310
});
13111311
});
13121312

1313+
describe('subqueries (parens)', () => {
1314+
test('can print complex subqueries with processing', () => {
1315+
const src =
1316+
'FROM index1, (FROM index2 | WHERE a > 10 | EVAL b = a * 2 | STATS cnt = COUNT(*) BY c | SORT cnt DESC | LIMIT 10), index3, (FROM index4 | STATS count(*)) | WHERE d > 10 | STATS max = max(*) BY e | SORT max DESC';
1317+
1318+
assertReprint(
1319+
src,
1320+
`FROM
1321+
index1,
1322+
(FROM index2
1323+
| WHERE a > 10
1324+
| EVAL b = a * 2
1325+
| STATS cnt = COUNT(*)
1326+
BY c
1327+
| SORT cnt DESC
1328+
| LIMIT 10),
1329+
index3,
1330+
(FROM index4 | STATS COUNT(*))
1331+
| WHERE d > 10
1332+
| STATS max = MAX(*)
1333+
BY e
1334+
| SORT max DESC`
1335+
);
1336+
});
1337+
});
1338+
13131339
test.todo('Idempotence on multiple times pretty printing');

src/platform/packages/shared/kbn-esql-ast/src/pretty_print/basic_pretty_printer.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,13 @@ export class BasicPrettyPrinter {
348348
return this.decorateWithComments(ctx.node, formatted);
349349
})
350350

351+
.on('visitParensExpression', (ctx) => {
352+
const child = ctx.visitChild();
353+
const formatted = `(${child})`;
354+
355+
return this.decorateWithComments(ctx.node, formatted);
356+
})
357+
351358
.on('visitFunctionCallExpression', (ctx) => {
352359
const opts = this.opts;
353360
const node = ctx.node;

src/platform/packages/shared/kbn-esql-ast/src/pretty_print/wrapping_pretty_printer.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,13 @@ export class WrappingPrettyPrinter {
679679
return this.decorateWithComments(inp, ctx.node, formatted);
680680
})
681681

682+
.on('visitParensExpression', (ctx, inp: Input): Output => {
683+
const child = ctx.visitChild(inp);
684+
const formatted = `(${child.txt.trimStart()})`;
685+
686+
return this.decorateWithComments(inp, ctx.node, formatted);
687+
})
688+
682689
.on('visitFunctionCallExpression', (ctx, inp: Input): Output => {
683690
const node = ctx.node;
684691
let operator = ctx.operator();
@@ -919,17 +926,10 @@ export class WrappingPrettyPrinter {
919926
}
920927

921928
let i = 0;
922-
let prevOut: Output | undefined;
923929
let hasCommands = false;
924930

925931
for (const out of ctx.visitCommands({ indent, remaining: remaining - indent.length })) {
926932
const isFirstCommand = i === 0;
927-
const isSecondCommand = i === 1;
928-
929-
if (isSecondCommand) {
930-
const firstCommandIsMultiline = prevOut?.lines && prevOut.lines > 1;
931-
if (firstCommandIsMultiline) text += '\n' + indent;
932-
}
933933

934934
const commandIndent = isFirstCommand ? indent : pipedCommandIndent;
935935
const topDecorations = this.printTopDecorations(commandIndent, commands[i]);
@@ -954,7 +954,6 @@ export class WrappingPrettyPrinter {
954954

955955
text += out.txt;
956956
i++;
957-
prevOut = out;
958957
hasCommands = true;
959958
}
960959

src/platform/packages/shared/kbn-esql-ast/src/visitor/global_visitor_context.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -545,8 +545,12 @@ export class GlobalVisitorContext<
545545
return this.visitQuery(parent, expressionNode, input as any);
546546
}
547547
case 'parens': {
548-
if (!this.methods.visitParensExpression) break;
549-
return this.visitParensExpression(parent, expressionNode, input as any);
548+
if (this.methods.visitParensExpression) {
549+
const result = this.visitParensExpression(parent, expressionNode, input as any);
550+
if (result) return result;
551+
}
552+
// Parens wraps subqueries: can return null to let comments attach to nodes inside the subquery
553+
break;
550554
}
551555
}
552556
return this.visitExpressionGeneric(parent, expressionNode, input as any);

src/platform/packages/shared/kbn-esql-ast/src/visitor/utils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,5 +86,9 @@ export function* children(node: ESQLProperNode): Iterable<ESQLAstExpression> {
8686
}
8787
break;
8888
}
89+
case 'parens': {
90+
yield node.child;
91+
break;
92+
}
8993
}
9094
}

src/platform/packages/shared/kbn-esql-ast/src/visitor/visitor.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,21 @@ export class Visitor<
9494
}
9595
return null;
9696
})
97+
.on('visitParensExpression', (ctx): ESQLProperNode | null => {
98+
const parens = ctx.node;
99+
const parensLocation = parens.location;
100+
101+
if (!parensLocation) {
102+
return null;
103+
}
104+
105+
// Handle position before opening "("
106+
if (parensLocation.min > pos) {
107+
return parens;
108+
}
109+
110+
return null;
111+
})
97112
.on('visitCommand', visitCommand)
98113
.on('visitHeaderCommand', visitCommand)
99114
.on('visitQuery', (ctx): ESQLProperNode | null => {
@@ -184,6 +199,31 @@ export class Visitor<
184199

185200
return null;
186201
})
202+
.on('visitParensExpression', (ctx): ESQLProperNode | null => {
203+
const parens = ctx.node;
204+
const parensLocation = parens.location;
205+
206+
if (!parensLocation) {
207+
return null;
208+
}
209+
210+
const childQuery = ctx.child();
211+
212+
// Handle comments between end of subquery content and closing ")"
213+
if (childQuery?.type === 'query' && childQuery.location) {
214+
if (childQuery.location.max < pos && pos <= parensLocation.max) {
215+
return parens;
216+
}
217+
}
218+
219+
// Handle comments immediately after closing ")"
220+
if (pos > parensLocation.max) {
221+
return parens;
222+
}
223+
224+
// For comments inside parens but before/during content, continue visiting children
225+
return null;
226+
})
187227
.on('visitCommand', visitCommand)
188228
.on('visitHeaderCommand', visitCommand)
189229
.on('visitQuery', (ctx): ESQLProperNode | null => {

0 commit comments

Comments
 (0)