Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ES|QL] Implement OrderExpression for SORT command arguments #189959

Merged
merged 35 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
89a8e0b
implement order expression
vadimkibana Aug 6, 2024
10461ba
add order expression function pseudo-definition
vadimkibana Aug 6, 2024
7bf612b
remove command pseudo-arguments
vadimkibana Aug 6, 2024
c46c733
setup sort command autocomplete suggestions
vadimkibana Aug 6, 2024
a53a0b9
implement sort command position finding routines
vadimkibana Aug 6, 2024
335c936
collect nulls and order matched text
vadimkibana Aug 8, 2024
ff4991a
complete basic sort suggestions
vadimkibana Aug 8, 2024
4ca63e4
implement partial modifier suggestions
vadimkibana Aug 8, 2024
1da7922
remove old sort tests
vadimkibana Aug 8, 2024
c4bd770
Merge remote-tracking branch 'upstream/main' into esql-order-expr
vadimkibana Aug 8, 2024
f5d9998
enambe pretty printing order tests
vadimkibana Aug 8, 2024
6d46b73
Merge remote-tracking branch 'upstream/main' into esql-order-expr
vadimkibana Aug 29, 2024
9363e4c
use type: order for order expressions
vadimkibana Aug 29, 2024
b3f57d9
add order expression support to visitor
vadimkibana Aug 29, 2024
fb70da9
support order expression in basic printer
vadimkibana Aug 29, 2024
ecc41ed
do not wrap expression in order expression if there are no modifiers
vadimkibana Aug 29, 2024
d69ef1b
remove sort tests from shared file
vadimkibana Aug 29, 2024
b04a590
fix typecheck errors
vadimkibana Aug 29, 2024
1d14898
Merge branch 'main' into esql-order-expr
vadimkibana Aug 30, 2024
9834058
Merge branch 'main' into esql-order-expr
elasticmachine Aug 30, 2024
0d16011
Merge branch 'main' into esql-order-expr
elasticmachine Sep 2, 2024
786e1b7
Merge branch 'main' into esql-order-expr
elasticmachine Sep 6, 2024
ecbb5d0
do complete suggestions
vadimkibana Sep 6, 2024
4608c2f
Merge branch 'main' into esql-order-expr
elasticmachine Sep 9, 2024
b92cd12
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine Sep 9, 2024
fc63d01
show comma and pipe after the last space
vadimkibana Sep 9, 2024
a85242a
Merge remote-tracking branch 'origin/esql-order-expr' into esql-order…
vadimkibana Sep 9, 2024
094bca4
Merge remote-tracking branch 'upstream/main' into esql-order-expr
vadimkibana Sep 9, 2024
87b5630
automatically trigger the next command
vadimkibana Sep 11, 2024
b28b4e8
show sort command field suggestions on space key
vadimkibana Sep 11, 2024
c782767
Merge branch 'main' into esql-order-expr
elasticmachine Sep 11, 2024
9c0a932
advance cursor on field selection
vadimkibana Sep 18, 2024
5ff4ebe
update tests
vadimkibana Sep 18, 2024
1889fc6
replace partial nulls suggestion in the right range
vadimkibana Sep 18, 2024
0cf2f71
Merge branch 'main' into esql-order-expr
elasticmachine Sep 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 122 additions & 19 deletions packages/kbn-esql-ast/src/__tests__/ast_parser.sort.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,107 @@ import { getAstAndSyntaxErrors as parse } from '../ast_parser';

describe('SORT', () => {
describe('correctly formatted', () => {
// Un-skip one https://github.com/elastic/kibana/issues/189491 fixed.
it.skip('example from documentation', () => {
it('sorting order without modifiers', () => {
const text = `FROM employees | SORT height`;
const { ast, errors } = parse(text);

expect(errors.length).toBe(0);
expect(ast).toMatchObject([
{},
{
type: 'command',
name: 'sort',
args: [
{
type: 'column',
name: 'height',
},
],
},
]);
});

it('sort expression is a function call', () => {
const text = `from a_index | sort values(textField)`;
const { ast, errors } = parse(text);

expect(errors.length).toBe(0);
expect(ast).toMatchObject([
{},
{
type: 'command',
name: 'sort',
args: [
{
type: 'function',
name: 'values',
},
],
},
]);
});

it('with order modifier "DESC"', () => {
const text = `
FROM employees
| KEEP first_name, last_name, height
| SORT height DESC
`;
const { ast, errors } = parse(text);

expect(errors.length).toBe(0);
expect(ast).toMatchObject([
{},
{
type: 'command',
name: 'sort',
args: [
{
type: 'order',
order: 'DESC',
nulls: '',
args: [
{
type: 'column',
name: 'height',
},
],
},
],
},
]);
});

it('with nulls modifier "NULLS LAST"', () => {
const text = `
FROM employees
| SORT height NULLS LAST
`;
const { ast, errors } = parse(text);

expect(errors.length).toBe(0);
expect(ast).toMatchObject([
{},
{
type: 'command',
name: 'sort',
args: [
{
type: 'column',
name: 'height',
type: 'order',
order: '',
nulls: 'NULLS LAST',
args: [
{
type: 'column',
name: 'height',
},
],
},
],
},
]);
});

// Un-skip once https://github.com/elastic/kibana/issues/189491 fixed.
it.skip('can parse various sorting columns with options', () => {
it('can parse various sorting columns with options', () => {
const text =
'FROM a | SORT a, b ASC, c DESC, d NULLS FIRST, e NULLS LAST, f ASC NULLS FIRST, g DESC NULLS LAST';
const { ast, errors } = parse(text);
Expand All @@ -55,28 +128,58 @@ describe('SORT', () => {
name: 'a',
},
{
type: 'column',
name: 'b',
order: 'ASC',
nulls: '',
args: [
{
name: 'b',
},
],
},
{
type: 'column',
name: 'c',
order: 'DESC',
nulls: '',
args: [
{
name: 'c',
},
],
},
{
type: 'column',
name: 'd',
order: '',
nulls: 'NULLS FIRST',
args: [
{
name: 'd',
},
],
},
{
type: 'column',
name: 'e',
order: '',
nulls: 'NULLS LAST',
args: [
{
name: 'e',
},
],
},
{
type: 'column',
name: 'f',
order: 'ASC',
nulls: 'NULLS FIRST',
args: [
{
name: 'f',
},
],
},
{
type: 'column',
name: 'g',
order: 'DESC',
nulls: 'NULLS LAST',
args: [
{
name: 'g',
},
],
},
],
},
Expand Down
4 changes: 2 additions & 2 deletions packages/kbn-esql-ast/src/ast_factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ import {
visitDissect,
visitGrok,
collectBooleanExpression,
visitOrderExpression,
visitOrderExpressions,
getPolicyName,
getMatchField,
getEnrichClauses,
Expand Down Expand Up @@ -238,7 +238,7 @@ export class AstListener implements ESQLParserListener {
exitSortCommand(ctx: SortCommandContext) {
const command = createCommand('sort', ctx);
this.ast.push(command);
command.args.push(...visitOrderExpression(ctx.orderExpression_list()));
command.args.push(...visitOrderExpressions(ctx.orderExpression_list()));
}

/**
Expand Down
21 changes: 21 additions & 0 deletions packages/kbn-esql-ast/src/ast_helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import type {
ESQLNumericLiteralType,
FunctionSubtype,
ESQLNumericLiteral,
ESQLOrderExpression,
} from './types';
import { parseIdentifier } from './parser/helpers';

Expand Down Expand Up @@ -222,6 +223,26 @@ export function createFunction<Subtype extends FunctionSubtype>(
return node;
}

export const createOrderExpression = (
ctx: ParserRuleContext,
arg: ESQLAstItem,
order: ESQLOrderExpression['order'],
nulls: ESQLOrderExpression['nulls']
) => {
const node: ESQLOrderExpression = {
type: 'order',
name: '',
order,
nulls,
args: [arg],
text: ctx.getText(),
location: getPosition(ctx.start, ctx.stop),
incomplete: Boolean(ctx.exception),
};

return node;
};

function walkFunctionStructure(
args: ESQLAstItem[],
initialLocation: ESQLLocation,
Expand Down
63 changes: 37 additions & 26 deletions packages/kbn-esql-ast/src/ast_walker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ import {
textExistsAndIsValid,
createInlineCast,
createUnknownItem,
createOrderExpression,
} from './ast_helpers';
import { getPosition } from './ast_position_utils';
import {
Expand All @@ -97,6 +98,7 @@ import {
ESQLUnnamedParamLiteral,
ESQLPositionalParamLiteral,
ESQLNamedParamLiteral,
ESQLOrderExpression,
} from './types';

export function collectAllSourceIdentifiers(ctx: FromCommandContext): ESQLAstItem[] {
Expand Down Expand Up @@ -608,34 +610,43 @@ export function visitByOption(
return [option];
}

export function visitOrderExpression(ctx: OrderExpressionContext[]) {
const ast: ESQLAstItem[] = [];
for (const orderCtx of ctx) {
const expression = collectBooleanExpression(orderCtx.booleanExpression());
if (orderCtx._ordering) {
const terminalNode =
orderCtx.getToken(esql_parser.ASC, 0) || orderCtx.getToken(esql_parser.DESC, 0);
const literal = createLiteral('string', terminalNode);
if (literal) {
expression.push(literal);
}
}
if (orderCtx.NULLS()) {
expression.push(createLiteral('string', orderCtx.NULLS()!)!);
if (orderCtx._nullOrdering && orderCtx._nullOrdering.text !== '<first missing>') {
const innerTerminalNode =
orderCtx.getToken(esql_parser.FIRST, 0) || orderCtx.getToken(esql_parser.LAST, 0);
const literal = createLiteral('string', innerTerminalNode);
if (literal) {
expression.push(literal);
}
}
}
const visitOrderExpression = (ctx: OrderExpressionContext): ESQLOrderExpression | ESQLAstItem => {
const arg = collectBooleanExpression(ctx.booleanExpression())[0];

if (expression.length) {
ast.push(...expression);
}
let order: ESQLOrderExpression['order'] = '';
let nulls: ESQLOrderExpression['nulls'] = '';

const ordering = ctx._ordering?.text?.toUpperCase();

if (ordering) order = ordering as ESQLOrderExpression['order'];

const nullOrdering = ctx._nullOrdering?.text?.toUpperCase();

switch (nullOrdering) {
case 'LAST':
nulls = 'NULLS LAST';
break;
case 'FIRST':
nulls = 'NULLS FIRST';
break;
}

if (!order && !nulls) {
return arg;
}

return createOrderExpression(ctx, arg, order, nulls);
};

export function visitOrderExpressions(
ctx: OrderExpressionContext[]
): Array<ESQLOrderExpression | ESQLAstItem> {
const ast: Array<ESQLOrderExpression | ESQLAstItem> = [];

for (const orderCtx of ctx) {
ast.push(visitOrderExpression(orderCtx));
}

return ast;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,22 @@ describe('single line query', () => {
expect(text).toBe('FROM a | SORT b');
});

/** @todo Enable once order expressions are supported. */
test.skip('order expression with ASC modifier', () => {
test('order expression with ASC modifier', () => {
const { text } = reprint('FROM a | SORT b ASC');

expect(text).toBe('FROM a | SORT b ASC');
});

/** @todo Enable once order expressions are supported. */
test.skip('order expression with ASC and NULLS FIRST modifier', () => {
const { text } = reprint('FROM a | SORT b ASC NULLS FIRST');
test('order expression with NULLS LAST modifier', () => {
const { text } = reprint('FROM a | SORT b NULLS LAST');

expect(text).toBe('FROM a | SORT b ASC NULLS FIRST');
expect(text).toBe('FROM a | SORT b NULLS LAST');
});

test('order expression with DESC and NULLS FIRST modifier', () => {
const { text } = reprint('FROM a | SORT b DESC NULLS FIRST');

expect(text).toBe('FROM a | SORT b DESC NULLS FIRST');
});
});

Expand Down
Loading