Skip to content

Commit

Permalink
[ES|QL] Adds METRICS source command AST node support in parsing (#1…
Browse files Browse the repository at this point in the history
…84494)

## Summary

Partially addresses #184498

- Sets up unit testing for the `@kbn/esql-ast` package.
- Adds unit tests for `FROM` command parsing.
- Adds `METRICS` command to AST node generation.
- Adds unit tests for `METRICS` command parsing.


### Checklist

Delete any items that are not applicable to this PR.

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
 
### For maintainers

- [x] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
  • Loading branch information
vadimkibana authored May 31, 2024
1 parent 8253d7e commit b3a12c1
Show file tree
Hide file tree
Showing 7 changed files with 396 additions and 5 deletions.
2 changes: 2 additions & 0 deletions packages/kbn-esql-ast/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
export type {
ESQLAst,
ESQLAstItem,
ESQLAstCommand,
ESQLAstMetricsCommand,
ESQLCommand,
ESQLCommandOption,
ESQLCommandMode,
Expand Down
13 changes: 13 additions & 0 deletions packages/kbn-esql-ast/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

module.exports = {
preset: '@kbn/test',
rootDir: '../..',
roots: ['<rootDir>/packages/kbn-esql-ast'],
};
160 changes: 160 additions & 0 deletions packages/kbn-esql-ast/src/__tests__/ast_parser.from.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { getAstAndSyntaxErrors as parse } from '../ast_parser';

describe('FROM', () => {
describe('correctly formatted', () => {
it('can parse basic FROM query', () => {
const text = 'FROM kibana_ecommerce_data';
const { ast, errors } = parse(text);

expect(errors.length).toBe(0);
expect(ast).toMatchObject([
{
type: 'command',
name: 'from',
args: [
{
type: 'source',
name: 'kibana_ecommerce_data',
sourceType: 'index',
},
],
},
]);
});

it('can parse FROM query with multiple index identifiers', () => {
const text = '\tFROM foo, bar \t\t, \n baz';
const { ast, errors } = parse(text);

expect(errors.length).toBe(0);
expect(ast).toMatchObject([
{
type: 'command',
name: 'from',
args: [
{
type: 'source',
name: 'foo',
sourceType: 'index',
},
{
type: 'source',
name: 'bar',
sourceType: 'index',
},
{
type: 'source',
name: 'baz',
sourceType: 'index',
},
],
},
]);
});

it('can parse FROM query with a single metadata column', () => {
const text = 'from foo METADATA bar';
const { ast, errors } = parse(text);

expect(errors.length).toBe(0);
expect(ast).toMatchObject([
{
type: 'command',
name: 'from',
args: [
{
type: 'source',
name: 'foo',
sourceType: 'index',
},
{
type: 'option',
name: 'metadata',
args: [
{
type: 'column',
name: 'bar',
quoted: false,
},
],
},
],
},
]);
});

it('can parse FROM query with multiple metadata columns', () => {
const text = 'from kibana_sample_data_ecommerce METADATA _index, \n _id\n';
const { ast, errors } = parse(text);

expect(errors.length).toBe(0);
expect(ast).toMatchObject([
{
type: 'command',
name: 'from',
args: [
{
type: 'source',
name: 'kibana_sample_data_ecommerce',
sourceType: 'index',
},
{
type: 'option',
name: 'metadata',
args: [
{
type: 'column',
name: '_index',
quoted: false,
},
{
type: 'column',
name: '_id',
quoted: false,
},
],
},
],
},
]);
});
});

describe('when incorrectly formatted, returns errors', () => {
it('when no index identifier specified', () => {
const text = 'FROM \n\t';
const { errors } = parse(text);

expect(errors.length > 0).toBe(true);
});

it('when comma is not followed by an index identifier', () => {
const text = '\tFROM foo, ';
const { errors } = parse(text);

expect(errors.length > 0).toBe(true);
});

it('when metadata has not columns', () => {
const text = 'from foo METADATA \t';
const { errors } = parse(text);

expect(errors.length > 0).toBe(true);
});

it('when metadata columns finish with a trailing comma', () => {
const text = 'from kibana_sample_data_ecommerce METADATA _index,';
const { errors } = parse(text);

expect(errors.length > 0).toBe(true);
});
});
});
166 changes: 166 additions & 0 deletions packages/kbn-esql-ast/src/__tests__/ast_parser.metrics.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { getAstAndSyntaxErrors as parse } from '../ast_parser';

describe('METRICS', () => {
describe('correctly formatted', () => {
it('can parse a basic query', () => {
const text = 'METRICS foo';
const { ast, errors } = parse(text);

expect(errors.length).toBe(0);
expect(ast).toMatchObject([
{
type: 'command',
name: 'metrics',
indices: [
{
type: 'source',
name: 'foo',
sourceType: 'index',
},
],
},
]);
});

it('can parse multiple "indices"', () => {
const text = 'METRICS foo ,\nbar\t,\t\nbaz \n';
const { ast, errors } = parse(text);

expect(errors.length).toBe(0);
expect(ast).toMatchObject([
{
type: 'command',
name: 'metrics',
indices: [
{
type: 'source',
name: 'foo',
sourceType: 'index',
},
{
type: 'source',
name: 'bar',
sourceType: 'index',
},
{
type: 'source',
name: 'baz',
sourceType: 'index',
},
],
},
]);
});

it('can parse "aggregates"', () => {
const text = 'metrics foo agg1, agg2';
const { ast, errors } = parse(text);

expect(errors.length).toBe(0);
expect(ast).toMatchObject([
{
type: 'command',
name: 'metrics',
indices: [
{
type: 'source',
name: 'foo',
sourceType: 'index',
},
],
aggregates: [
{
type: 'column',
text: 'agg1',
},
{
type: 'column',
text: 'agg2',
},
],
},
]);
});

it('can parse "grouping"', () => {
const text = 'mEtRiCs foo agg BY grp1, grp2';
const { ast, errors } = parse(text);

expect(errors.length).toBe(0);
expect(ast).toMatchObject([
{
type: 'command',
name: 'metrics',
indices: [
{
type: 'source',
name: 'foo',
sourceType: 'index',
},
],
aggregates: [
{
type: 'column',
text: 'agg',
},
],
grouping: [
{
type: 'column',
text: 'grp1',
},
{
type: 'column',
text: 'grp2',
},
],
},
]);
});
});

describe('when incorrectly formatted, returns errors', () => {
it('when no index identifier specified', () => {
const text = 'METRICS \n\t';
const { errors } = parse(text);

expect(errors.length > 0).toBe(true);
});

it('when comma follows index identifier', () => {
const text = 'METRICS foo, ';
const { errors } = parse(text);

expect(errors.length > 0).toBe(true);
});

it('when comma follows "aggregates"', () => {
const text = 'from foo agg1, agg2';
const { errors } = parse(text);

expect(errors.length > 0).toBe(true);
});

it('when "grouping" in BY clause is empty', () => {
const text = 'from foo agg1, agg2 BY \t';
const { errors } = parse(text);

expect(errors.length > 0).toBe(true);
});

it('when "grouping" has trailing comma', () => {
const text = 'from foo agg1, agg2 BY grp1, grp2,';
const { errors } = parse(text);

expect(errors.length > 0).toBe(true);
});
});
});
Loading

0 comments on commit b3a12c1

Please sign in to comment.