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] Support ES|QL paramters in function names #198486

Merged
merged 4 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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