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] Update script to sync operators automatically #200115

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from 5 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
Original file line number Diff line number Diff line change
Expand Up @@ -299,9 +299,116 @@ function getFunctionDefinition(ESFunctionDefinition: Record<string, any>): Funct
return ret as FunctionDefinition;
}

const operatorNames = {
add: '+',
sub: '-',
div: '/',
equals: '==',
greater_than: '>',
greater_than_or_equal: '>=',
less_than: '<',
less_than_or_equal: '<=',
not_equals: '!=',
mod: '%',
mul: '*',
};
const validators: Record<'div' | 'mod', FunctionDefinition['validate']> = {
div: `(fnDef) => {
const [left, right] = fnDef.args;
const messages = [];
if (!Array.isArray(left) && !Array.isArray(right)) {
if (right.type === 'literal' && isNumericType(right.literalType)) {
if (right.value === 0) {
messages.push({
type: 'warning' as const,
code: 'divideByZero',
text: i18n.translate(
'kbn-esql-validation-autocomplete.esql.divide.warning.divideByZero',
{
defaultMessage: 'Cannot divide by zero: {left}/{right}',
values: {
left: left.text,
right: right.value,
},
}
),
location: fnDef.location,
});
}
}
}
return messages;
}`,
mod: `(fnDef) => {
const [left, right] = fnDef.args;
const messages = [];
if (!Array.isArray(left) && !Array.isArray(right)) {
if (right.type === 'literal' && isNumericType(right.literalType)) {
if (right.value === 0) {
messages.push({
type: 'warning' as const,
code: 'moduleByZero',
text: i18n.translate(
'kbn-esql-validation-autocomplete.esql.divide.warning.zeroModule',
{
defaultMessage: 'Module by zero can return null value: {left}%{right}',
values: {
left: left.text,
right: right.value,
},
}
),
location: fnDef.location,
});
}
}
}
return messages;
}`,
};

/**
* Elasticsearch doc exports name as 'lhs' or 'rhs' instead of 'left' or 'right'
* @param str
* @returns
*/
const replaceParamName = (str: string) => {
switch (str) {
case 'lhs':
return 'left';
case 'rhs':
return 'right';
default:
return str;
}
};

const enrichOperators = (operatorsFunctionDefinitions: FunctionDefinition[]) => {
return operatorsFunctionDefinitions.map((op) => {
const isMathOperator = ['add', 'sub', 'div', 'mod', 'mul'].includes(op.name);
return {
...op,
signatures: op.signatures.map((s) => ({
...s,
// Elasticsearch docs uses lhs and rhs instead of left and right that Kibana code uses
params: s.params.map((param) => ({ ...param, name: replaceParamName(param.name) })),
})),
// Elasticsearch docs does not include the full supported commands for math operators
// so we are overriding to add proper support
supportedCommands: isMathOperator
? ['eval', 'where', 'row', 'stats', 'metrics', 'sort']
: op.supportedCommands,
supportedOptions: isMathOperator ? ['by'] : op.supportedOptions,
// @TODO: change to operator type
type: 'builtin',
validate: validators[op.name],
};
});
};

function printGeneratedFunctionsFile(
functionDefinitions: FunctionDefinition[],
functionsType: 'aggregation' | 'scalar'
functionsType: 'aggregation' | 'scalar' | 'operators'
) {
/**
* Deals with asciidoc internal cross-references in the function descriptions
Expand Down Expand Up @@ -348,7 +455,7 @@ function printGeneratedFunctionsFile(
return `// Do not edit this manually... generated by scripts/generate_function_definitions.ts
const ${getDefinitionName(name)}: FunctionDefinition = {
type: '${type}',
name: '${name}',
name: '${operatorNames[name] ?? name}',
description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.${name}', { defaultMessage: ${JSON.stringify(
removeAsciiDocInternalCrossReferences(removeInlineAsciiDocLinks(description), functionNames)
)} }),${functionDefinition.ignoreAsSuggestion ? 'ignoreAsSuggestion: true,\n' : ''}
Expand Down Expand Up @@ -389,6 +496,12 @@ ${
import { isLiteralItem } from '../../shared/helpers';`
: ''
}
${
functionsType === 'operators'
? `import type { isNumericType } from '../../shared/esql_types';`
: ''
}



`;
Expand Down Expand Up @@ -428,13 +541,17 @@ import { isLiteralItem } from '../../shared/helpers';`

const scalarFunctionDefinitions: FunctionDefinition[] = [];
const aggFunctionDefinitions: FunctionDefinition[] = [];
const operatorDefinitions: FunctionDefinition[] = [];

for (const ESDefinition of ESFunctionDefinitions) {
if (aliases.has(ESDefinition.name) || excludedFunctions.has(ESDefinition.name)) {
continue;
}

const functionDefinition = getFunctionDefinition(ESDefinition);

if (functionDefinition.type === 'operator') {
operatorDefinitions.push(functionDefinition);
}
if (functionDefinition.type === 'eval') {
scalarFunctionDefinitions.push(functionDefinition);
} else if (functionDefinition.type === 'agg') {
Expand All @@ -452,4 +569,8 @@ import { isLiteralItem } from '../../shared/helpers';`
join(__dirname, '../src/definitions/generated/aggregation_functions.ts'),
printGeneratedFunctionsFile(aggFunctionDefinitions, 'aggregation')
);
await writeFile(
join(__dirname, '../src/definitions/generated/operators.ts'),
printGeneratedFunctionsFile(enrichOperators(operatorDefinitions), 'operators')
);
})();
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import { i18n } from '@kbn/i18n';
import { ESQL_NUMBER_TYPES, isNumericType } from '../shared/esql_types';
import type { FunctionDefinition, FunctionParameterType, FunctionReturnType } from './types';

import { operatorsFunctionDefinitions } from './generated/operators';
type MathFunctionSignature = [FunctionParameterType, FunctionParameterType, FunctionReturnType];

function createMathDefinition(
Expand Down Expand Up @@ -384,7 +384,7 @@ export const comparisonFunctions: FunctionDefinition[] = [
},
].map((op): FunctionDefinition => createComparisonDefinition(op));

const likeFunctions: FunctionDefinition[] = [
const notLikeFunctions: FunctionDefinition[] = [
// Skip the insensitive case equality until it gets restored back
// new special comparison operator for strings only
// {
Expand All @@ -393,24 +393,12 @@ const likeFunctions: FunctionDefinition[] = [
// defaultMessage: 'Case insensitive equality',
// }),
// },
{
name: 'like',
description: i18n.translate('kbn-esql-validation-autocomplete.esql.definition.likeDoc', {
defaultMessage: 'Filter data based on string patterns',
}),
},
{ name: 'not_like', description: '' },
{
name: 'rlike',
description: i18n.translate('kbn-esql-validation-autocomplete.esql.definition.rlikeDoc', {
defaultMessage: 'Filter data based on string regular expressions',
}),
},
{ name: 'not_rlike', description: '' },
].map(({ name, description }) => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stratoula Should we keep the internationalized descriptions for these functions? If we import the descriptions from Elasticsearch following this PR, I don't think they will be internationalized.

Copy link
Contributor

@stratoula stratoula Nov 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const def: FunctionDefinition = {
type: 'builtin' as const,
ignoreAsSuggestion: /not/.test(name),
ignoreAsSuggestion: true,
name,
description,
supportedCommands: ['eval', 'where', 'row', 'sort'],
Expand Down Expand Up @@ -450,14 +438,7 @@ const likeFunctions: FunctionDefinition[] = [
return def;
});

const inFunctions: FunctionDefinition[] = [
{
name: 'in',
description: i18n.translate('kbn-esql-validation-autocomplete.esql.definition.inDoc', {
defaultMessage:
'Tests if the value an expression takes is contained in a list of other expressions',
}),
},
const notInFunctions: FunctionDefinition[] = [
{ name: 'not_in', description: '' },
].map<FunctionDefinition>(({ name, description }) => ({
// set all arrays to type "any" for now
Expand All @@ -470,7 +451,7 @@ const inFunctions: FunctionDefinition[] = [
//
// we need to revisit with more robust validation
type: 'builtin',
ignoreAsSuggestion: /not/.test(name),
ignoreAsSuggestion: true,
name,
description,
supportedCommands: ['eval', 'where', 'row', 'sort'],
Expand Down Expand Up @@ -645,10 +626,9 @@ const otherDefinitions: FunctionDefinition[] = [
];

export const builtinFunctions: FunctionDefinition[] = [
...mathFunctions,
...comparisonFunctions,
...likeFunctions,
...inFunctions,
...operatorsFunctionDefinitions,
...notInFunctions,
...notLikeFunctions,
...logicalOperators,
...nullFunctions,
...otherDefinitions,
Expand Down
Loading