mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
# Backport This will backport the following commits from `main` to `8.x`: - [[ES|QL] Match expression support in the ES|QL AST package (#215336)](https://github.com/elastic/kibana/pull/215336) <!--- Backport version: 9.6.6 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Vadim Kibana","email":"82822460+vadimkibana@users.noreply.github.com"},"sourceCommit":{"committedDate":"2025-03-20T16:19:45Z","message":"[ES|QL] Match expression support in the ES|QL AST package (#215336)\n\n## Summary\n\nPartially addresses https://github.com/elastic/kibana/issues/214359\n\n- Adds support for *MatchExpression* in `WHERE` command.\n - `WHERE column :: cast : condition`\n - Support for cast and condition parsing\n - Support for pretty-printing\n\n\n\n### Checklist\n\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios","sha":"c8abafc6e7a63beeed404b0ea1d2a4dfd7777dc2","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["review","release_note:skip","Feature:ES|QL","Team:ESQL","backport:version","v9.1.0","v8.19.0"],"title":"[ES|QL] Match expression support in the ES|QL AST package","number":215336,"url":"https://github.com/elastic/kibana/pull/215336","mergeCommit":{"message":"[ES|QL] Match expression support in the ES|QL AST package (#215336)\n\n## Summary\n\nPartially addresses https://github.com/elastic/kibana/issues/214359\n\n- Adds support for *MatchExpression* in `WHERE` command.\n - `WHERE column :: cast : condition`\n - Support for cast and condition parsing\n - Support for pretty-printing\n\n\n\n### Checklist\n\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios","sha":"c8abafc6e7a63beeed404b0ea1d2a4dfd7777dc2"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/215336","number":215336,"mergeCommit":{"message":"[ES|QL] Match expression support in the ES|QL AST package (#215336)\n\n## Summary\n\nPartially addresses https://github.com/elastic/kibana/issues/214359\n\n- Adds support for *MatchExpression* in `WHERE` command.\n - `WHERE column :: cast : condition`\n - Support for cast and condition parsing\n - Support for pretty-printing\n\n\n\n### Checklist\n\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios","sha":"c8abafc6e7a63beeed404b0ea1d2a4dfd7777dc2"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Vadim Kibana <82822460+vadimkibana@users.noreply.github.com>
This commit is contained in:
parent
915a7f8549
commit
0a7f9f74dc
5 changed files with 187 additions and 4 deletions
|
@ -8,6 +8,7 @@
|
|||
*/
|
||||
|
||||
import { parse } from '..';
|
||||
import { ESQLColumn, ESQLCommand, ESQLFunction, ESQLInlineCast } from '../../types';
|
||||
|
||||
describe('WHERE', () => {
|
||||
describe('correctly formatted', () => {
|
||||
|
@ -35,5 +36,114 @@ describe('WHERE', () => {
|
|||
},
|
||||
]);
|
||||
});
|
||||
|
||||
describe('match expression', () => {
|
||||
it('simple column name', () => {
|
||||
const text = `FROM index | WHERE abc`;
|
||||
const { root } = parse(text);
|
||||
|
||||
expect(root.commands[1]).toMatchObject({
|
||||
type: 'command',
|
||||
name: 'where',
|
||||
args: [
|
||||
{
|
||||
type: 'column',
|
||||
name: 'abc',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('simple column with match expression', () => {
|
||||
const text = `FROM index | WHERE abc : 123`;
|
||||
const { root } = parse(text);
|
||||
|
||||
expect(root.commands[1]).toMatchObject({
|
||||
type: 'command',
|
||||
name: 'where',
|
||||
args: [
|
||||
{
|
||||
type: 'function',
|
||||
subtype: 'binary-expression',
|
||||
name: ':',
|
||||
args: [
|
||||
{
|
||||
type: 'column',
|
||||
name: 'abc',
|
||||
},
|
||||
{
|
||||
type: 'literal',
|
||||
literalType: 'integer',
|
||||
value: 123,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('correctly reports match expression location', () => {
|
||||
const text = `FROM index | WHERE abc /*a*/ : /*a*/ 123`;
|
||||
const { root } = parse(text);
|
||||
const expression = root.commands[1].args[0] as ESQLFunction;
|
||||
|
||||
expect(expression.name).toBe(':');
|
||||
expect(text.slice(expression.location.min, expression.location.max + 1)).toBe(
|
||||
'abc /*a*/ : /*a*/ 123'
|
||||
);
|
||||
});
|
||||
|
||||
it('simple column with match expression and inline cast', () => {
|
||||
const text = `FROM index | WHERE abc :: INTEGER : 123`;
|
||||
const { root } = parse(text);
|
||||
|
||||
expect(root.commands[1]).toMatchObject({
|
||||
type: 'command',
|
||||
name: 'where',
|
||||
args: [
|
||||
{
|
||||
type: 'function',
|
||||
subtype: 'binary-expression',
|
||||
name: ':',
|
||||
args: [
|
||||
{
|
||||
type: 'inlineCast',
|
||||
castType: 'integer',
|
||||
value: {
|
||||
type: 'column',
|
||||
name: 'abc',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'literal',
|
||||
literalType: 'integer',
|
||||
value: 123,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('correctly reports match expression with inline cast location', () => {
|
||||
const text = `FROM index | WHERE abc /*a*/ :: /*a*/ INTEGER : 123`;
|
||||
const { root } = parse(text);
|
||||
const command = root.commands[1] as ESQLCommand;
|
||||
const match = command.args[0] as ESQLFunction;
|
||||
const cast = match.args[0] as ESQLInlineCast;
|
||||
const column = cast.value as ESQLColumn;
|
||||
|
||||
expect(text.slice(command.location.min, command.location.max + 1)).toBe(
|
||||
'WHERE abc /*a*/ :: /*a*/ INTEGER : 123'
|
||||
);
|
||||
expect(text.slice(match.location.min, match.location.max + 1)).toBe(
|
||||
'abc /*a*/ :: /*a*/ INTEGER : 123'
|
||||
);
|
||||
expect(text.slice(cast.location.min, cast.location.max + 1)).toBe(
|
||||
'abc /*a*/ :: /*a*/ INTEGER'
|
||||
);
|
||||
expect(text.slice(column.location.min, column.location.max + 1)).toBe('abc');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -51,7 +51,6 @@ import {
|
|||
visitByOption,
|
||||
collectAllColumnIdentifiers,
|
||||
visitRenameClauses,
|
||||
collectBooleanExpression,
|
||||
visitOrderExpressions,
|
||||
getPolicyName,
|
||||
getMatchField,
|
||||
|
@ -63,6 +62,7 @@ import { createDissectCommand } from './factories/dissect';
|
|||
import { createGrokCommand } from './factories/grok';
|
||||
import { createStatsCommand } from './factories/stats';
|
||||
import { createChangePointCommand } from './factories/change_point';
|
||||
import { createWhereCommand } from './factories/where';
|
||||
|
||||
export class ESQLAstBuilderListener implements ESQLParserListener {
|
||||
private ast: ESQLAst = [];
|
||||
|
@ -102,9 +102,9 @@ export class ESQLAstBuilderListener implements ESQLParserListener {
|
|||
* @param ctx the parse tree
|
||||
*/
|
||||
exitWhereCommand(ctx: WhereCommandContext) {
|
||||
const command = createCommand('where', ctx);
|
||||
const command = createWhereCommand(ctx);
|
||||
|
||||
this.ast.push(command);
|
||||
command.args.push(...collectBooleanExpression(ctx.booleanExpression()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { WhereCommandContext } from '../../antlr/esql_parser';
|
||||
import { ESQLCommand } from '../../types';
|
||||
import { createCommand } from '../factories';
|
||||
import { collectBooleanExpression } from '../walkers';
|
||||
|
||||
export const createWhereCommand = (ctx: WhereCommandContext): ESQLCommand<'where'> => {
|
||||
const command = createCommand('where', ctx);
|
||||
|
||||
const expressions = collectBooleanExpression(ctx.booleanExpression());
|
||||
|
||||
command.args.push(expressions[0]);
|
||||
|
||||
return command;
|
||||
};
|
|
@ -59,6 +59,8 @@ import {
|
|||
InlineCastContext,
|
||||
IndexPatternContext,
|
||||
InlinestatsCommandContext,
|
||||
MatchExpressionContext,
|
||||
MatchBooleanExpressionContext,
|
||||
} from '../antlr/esql_parser';
|
||||
import {
|
||||
createSource,
|
||||
|
@ -83,6 +85,7 @@ import {
|
|||
createFunctionCall,
|
||||
createParam,
|
||||
createLiteralString,
|
||||
createBinaryExpression,
|
||||
} from './factories';
|
||||
|
||||
import {
|
||||
|
@ -94,8 +97,12 @@ import {
|
|||
ESQLAstField,
|
||||
ESQLInlineCast,
|
||||
ESQLOrderExpression,
|
||||
ESQLBinaryExpression,
|
||||
InlineCastingType,
|
||||
} from '../types';
|
||||
import { firstItem, lastItem } from '../visitor/utils';
|
||||
import { Builder } from '../builder';
|
||||
import { getPosition } from './helpers';
|
||||
|
||||
export function collectAllSourceIdentifiers(ctx: FromCommandContext): ESQLAstItem[] {
|
||||
const fromContexts = ctx.getTypedRuleContexts(IndexPatternContext);
|
||||
|
@ -512,9 +519,15 @@ function collectDefaultExpression(ctx: BooleanExpressionContext) {
|
|||
|
||||
export function collectBooleanExpression(ctx: BooleanExpressionContext | undefined): ESQLAstItem[] {
|
||||
const ast: ESQLAstItem[] = [];
|
||||
|
||||
if (!ctx) {
|
||||
return ast;
|
||||
}
|
||||
|
||||
if (ctx instanceof MatchExpressionContext) {
|
||||
return [visitMatchExpression(ctx)];
|
||||
}
|
||||
|
||||
return ast
|
||||
.concat(
|
||||
collectLogicalExpression(ctx),
|
||||
|
@ -525,6 +538,41 @@ export function collectBooleanExpression(ctx: BooleanExpressionContext | undefin
|
|||
.flat();
|
||||
}
|
||||
|
||||
type ESQLAstMatchBooleanExpression = ESQLColumn | ESQLBinaryExpression | ESQLInlineCast;
|
||||
|
||||
const visitMatchExpression = (ctx: MatchExpressionContext): ESQLAstMatchBooleanExpression => {
|
||||
return visitMatchBooleanExpression(ctx.matchBooleanExpression());
|
||||
};
|
||||
|
||||
const visitMatchBooleanExpression = (
|
||||
ctx: MatchBooleanExpressionContext
|
||||
): ESQLAstMatchBooleanExpression => {
|
||||
let expression: ESQLAstMatchBooleanExpression = createColumn(ctx.qualifiedName());
|
||||
const dataTypeCtx = ctx.dataType();
|
||||
const constantCtx = ctx.constant();
|
||||
|
||||
if (dataTypeCtx) {
|
||||
expression = Builder.expression.inlineCast(
|
||||
{
|
||||
castType: dataTypeCtx.getText().toLowerCase() as InlineCastingType,
|
||||
value: expression,
|
||||
},
|
||||
{
|
||||
location: getPosition(ctx.start, dataTypeCtx.stop),
|
||||
incomplete: Boolean(ctx.exception),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (constantCtx) {
|
||||
const constantExpression = getConstant(constantCtx);
|
||||
|
||||
expression = createBinaryExpression(':', ctx, [expression, constantExpression]);
|
||||
}
|
||||
|
||||
return expression;
|
||||
};
|
||||
|
||||
export function visitField(ctx: FieldContext) {
|
||||
if (ctx.qualifiedName() && ctx.ASSIGN()) {
|
||||
const fn = createFunction(ctx.ASSIGN()!.getText(), ctx, undefined, 'binary-expression');
|
||||
|
|
|
@ -215,7 +215,8 @@ export type BinaryExpressionOperator =
|
|||
| BinaryExpressionComparisonOperator
|
||||
| BinaryExpressionRegexOperator
|
||||
| BinaryExpressionRenameOperator
|
||||
| BinaryExpressionWhereOperator;
|
||||
| BinaryExpressionWhereOperator
|
||||
| BinaryExpressionMatchOperator;
|
||||
|
||||
export type BinaryExpressionArithmeticOperator = '+' | '-' | '*' | '/' | '%';
|
||||
export type BinaryExpressionAssignmentOperator = '=';
|
||||
|
@ -223,6 +224,7 @@ export type BinaryExpressionComparisonOperator = '==' | '=~' | '!=' | '<' | '<='
|
|||
export type BinaryExpressionRegexOperator = 'like' | 'not_like' | 'rlike' | 'not_rlike';
|
||||
export type BinaryExpressionRenameOperator = 'as';
|
||||
export type BinaryExpressionWhereOperator = 'where';
|
||||
export type BinaryExpressionMatchOperator = ':';
|
||||
|
||||
// from https://github.com/elastic/elasticsearch/blob/122e7288200ee03e9087c98dff6cebbc94e774aa/docs/reference/esql/functions/kibana/inline_cast.json
|
||||
export type InlineCastingType =
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue