Skip to content

Commit

Permalink
[ES|QL] Support ES|QL paramters in function names (#198486)
Browse files Browse the repository at this point in the history
## Summary

Partially addresses
#198251

- Improves for `param` node parsing inside `function` call nodes.
- Adds the new `identifier` node type.
- The `function` AST nodes now have `operator` property, which contains
either a `param` or `identifier` node.

### Checklist

- [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#_add_your_labels)

(cherry picked from commit 750452e)
  • Loading branch information
vadimkibana committed Oct 31, 2024
1 parent 1f6547b commit 2e846c6
Show file tree
Hide file tree
Showing 5 changed files with 249 additions and 60 deletions.
65 changes: 65 additions & 0 deletions packages/kbn-esql-ast/src/builder/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@ import {
ESQLCommand,
ESQLCommandOption,
ESQLDecimalLiteral,
ESQLIdentifier,
ESQLInlineCast,
ESQLIntegerLiteral,
ESQLList,
ESQLLocation,
ESQLNamedParamLiteral,
ESQLParam,
ESQLPositionalParamLiteral,
ESQLOrderExpression,
ESQLSource,
} from '../types';
Expand Down Expand Up @@ -190,4 +194,65 @@ export namespace Builder {
};
}
}

export const identifier = (
template: AstNodeTemplate<ESQLIdentifier>,
fromParser?: Partial<AstNodeParserFields>
): ESQLIdentifier => {
return {
...template,
...Builder.parserFields(fromParser),
type: 'identifier',
};
};

export namespace param {
export const unnamed = (fromParser?: Partial<AstNodeParserFields>): ESQLParam => {
const node = {
...Builder.parserFields(fromParser),
name: '',
value: '',
paramType: 'unnamed',
type: 'literal',
literalType: 'param',
};

return node as ESQLParam;
};

export const named = (
template: Omit<AstNodeTemplate<ESQLNamedParamLiteral>, 'name' | 'literalType' | 'paramType'>,
fromParser?: Partial<AstNodeParserFields>
): ESQLNamedParamLiteral => {
const node: ESQLNamedParamLiteral = {
...template,
...Builder.parserFields(fromParser),
name: '',
type: 'literal',
literalType: 'param',
paramType: 'named',
};

return node;
};

export const positional = (
template: Omit<
AstNodeTemplate<ESQLPositionalParamLiteral>,
'name' | 'literalType' | 'paramType'
>,
fromParser?: Partial<AstNodeParserFields>
): ESQLPositionalParamLiteral => {
const node: ESQLPositionalParamLiteral = {
...template,
...Builder.parserFields(fromParser),
name: '',
type: 'literal',
literalType: 'param',
paramType: 'positional',
};

return node;
};
}
}
97 changes: 97 additions & 0 deletions packages/kbn-esql-ast/src/parser/__tests__/function.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,103 @@ describe('function AST nodes', () => {
},
]);
});

it('parses out function name as identifier node', () => {
const query = 'ROW fn(1, 2, 3)';
const { ast, errors } = parse(query);

expect(errors.length).toBe(0);
expect(ast).toMatchObject([
{
type: 'command',
name: 'row',
args: [
{
type: 'function',
name: 'fn',
operator: {
type: 'identifier',
name: 'fn',
},
},
],
},
]);
});

it('parses out function name as named param', () => {
const query = 'ROW ?insert_here(1, 2, 3)';
const { ast, errors } = parse(query);

expect(errors.length).toBe(0);
expect(ast).toMatchObject([
{
type: 'command',
name: 'row',
args: [
{
type: 'function',
name: '?insert_here',
operator: {
type: 'literal',
literalType: 'param',
paramType: 'named',
value: 'insert_here',
},
},
],
},
]);
});

it('parses out function name as unnamed param', () => {
const query = 'ROW ?(1, 2, 3)';
const { ast, errors } = parse(query);

expect(errors.length).toBe(0);
expect(ast).toMatchObject([
{
type: 'command',
name: 'row',
args: [
{
type: 'function',
name: '?',
operator: {
type: 'literal',
literalType: 'param',
paramType: 'unnamed',
},
},
],
},
]);
});

it('parses out function name as positional param', () => {
const query = 'ROW ?30035(1, 2, 3)';
const { ast, errors } = parse(query);

expect(errors.length).toBe(0);
expect(ast).toMatchObject([
{
type: 'command',
name: 'row',
args: [
{
type: 'function',
name: '?30035',
operator: {
type: 'literal',
literalType: 'param',
paramType: 'positional',
value: 30035,
},
},
],
},
]);
});
});

describe('"unary-expression"', () => {
Expand Down
68 changes: 67 additions & 1 deletion packages/kbn-esql-ast/src/parser/factories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@
* In case of changes in the grammar, this script should be updated: esql_update_ast_script.js
*/

import type { Token, ParserRuleContext, TerminalNode, RecognitionException } from 'antlr4';
import type {
Token,
ParserRuleContext,
TerminalNode,
RecognitionException,
ParseTree,
} from 'antlr4';
import {
IndexPatternContext,
QualifiedNameContext,
Expand All @@ -21,6 +27,10 @@ import {
type IntegerValueContext,
type QualifiedIntegerLiteralContext,
QualifiedNamePatternContext,
FunctionContext,
IdentifierContext,
InputParamContext,
InputNamedOrPositionalParamContext,
} from '../antlr/esql_parser';
import { DOUBLE_TICKS_REGEX, SINGLE_BACKTICK, TICKS_REGEX } from './constants';
import type {
Expand All @@ -42,6 +52,8 @@ import type {
ESQLNumericLiteral,
ESQLOrderExpression,
InlineCastingType,
ESQLFunctionCallExpression,
ESQLIdentifier,
} from '../types';
import { parseIdentifier, getPosition } from './helpers';
import { Builder, type AstNodeParserFields } from '../builder';
Expand Down Expand Up @@ -201,6 +213,60 @@ export function createFunction<Subtype extends FunctionSubtype>(
return node;
}

export const createFunctionCall = (ctx: FunctionContext): ESQLFunctionCallExpression => {
const functionExpressionCtx = ctx.functionExpression();
const functionName = functionExpressionCtx.functionName();
const node: ESQLFunctionCallExpression = {
type: 'function',
subtype: 'variadic-call',
name: functionName.getText().toLowerCase(),
text: ctx.getText(),
location: getPosition(ctx.start, ctx.stop),
args: [],
incomplete: Boolean(ctx.exception),
};

const identifierOrParameter = functionName.identifierOrParameter();
if (identifierOrParameter) {
const identifier = identifierOrParameter.identifier();
if (identifier) {
node.operator = createIdentifier(identifier);
} else {
const parameter = identifierOrParameter.parameter();
if (parameter) {
node.operator = createParam(parameter);
}
}
}

return node;
};

const createIdentifier = (identifier: IdentifierContext): ESQLIdentifier => {
return Builder.identifier(
{ name: identifier.getText().toLowerCase() },
createParserFields(identifier)
);
};

export const createParam = (ctx: ParseTree) => {
if (ctx instanceof InputParamContext) {
return Builder.param.unnamed(createParserFields(ctx));
} else if (ctx instanceof InputNamedOrPositionalParamContext) {
const text = ctx.getText();
const value = text.slice(1);
const valueAsNumber = Number(value);
const isPositional = String(valueAsNumber) === value;
const parserFields = createParserFields(ctx);

if (isPositional) {
return Builder.param.positional({ value: valueAsNumber }, parserFields);
} else {
return Builder.param.named({ value }, parserFields);
}
}
};

export const createOrderExpression = (
ctx: ParserRuleContext,
arg: ESQLColumn,
Expand Down
64 changes: 5 additions & 59 deletions packages/kbn-esql-ast/src/parser/walkers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,6 @@ import {
type ValueExpressionContext,
ValueExpressionDefaultContext,
InlineCastContext,
InputNamedOrPositionalParamContext,
InputParamContext,
IndexPatternContext,
InlinestatsCommandContext,
} from '../antlr/esql_parser';
Expand All @@ -86,8 +84,9 @@ import {
createInlineCast,
createUnknownItem,
createOrderExpression,
createFunctionCall,
createParam,
} from './factories';
import { getPosition } from './helpers';

import {
ESQLLiteral,
Expand All @@ -97,9 +96,6 @@ import {
ESQLAstItem,
ESQLAstField,
ESQLInlineCast,
ESQLUnnamedParamLiteral,
ESQLPositionalParamLiteral,
ESQLNamedParamLiteral,
ESQLOrderExpression,
} from '../types';
import { firstItem, lastItem } from '../visitor/utils';
Expand Down Expand Up @@ -390,50 +386,8 @@ function getConstant(ctx: ConstantContext): ESQLAstItem {
const values: ESQLLiteral[] = [];

for (const child of ctx.children) {
if (child instanceof InputParamContext) {
const literal: ESQLUnnamedParamLiteral = {
type: 'literal',
literalType: 'param',
paramType: 'unnamed',
text: ctx.getText(),
name: '',
value: '',
location: getPosition(ctx.start, ctx.stop),
incomplete: Boolean(ctx.exception),
};
values.push(literal);
} else if (child instanceof InputNamedOrPositionalParamContext) {
const text = child.getText();
const value = text.slice(1);
const valueAsNumber = Number(value);
const isPositional = String(valueAsNumber) === value;

if (isPositional) {
const literal: ESQLPositionalParamLiteral = {
type: 'literal',
literalType: 'param',
paramType: 'positional',
value: valueAsNumber,
text,
name: '',
location: getPosition(ctx.start, ctx.stop),
incomplete: Boolean(ctx.exception),
};
values.push(literal);
} else {
const literal: ESQLNamedParamLiteral = {
type: 'literal',
literalType: 'param',
paramType: 'named',
value,
text,
name: '',
location: getPosition(ctx.start, ctx.stop),
incomplete: Boolean(ctx.exception),
};
values.push(literal);
}
}
const param = createParam(child);
if (param) values.push(param);
}

return values;
Expand Down Expand Up @@ -478,15 +432,7 @@ export function visitPrimaryExpression(ctx: PrimaryExpressionContext): ESQLAstIt
}
if (ctx instanceof FunctionContext) {
const functionExpressionCtx = ctx.functionExpression();
const functionNameContext = functionExpressionCtx.functionName().MATCH()
? functionExpressionCtx.functionName().MATCH()
: functionExpressionCtx.functionName().identifierOrParameter();
const fn = createFunction(
functionNameContext.getText().toLowerCase(),
ctx,
undefined,
'variadic-call'
);
const fn = createFunctionCall(ctx);
const asteriskArg = functionExpressionCtx.ASTERISK()
? createColumnStar(functionExpressionCtx.ASTERISK()!)
: undefined;
Expand Down
Loading

0 comments on commit 2e846c6

Please sign in to comment.