Revert "[ES|QL] Add improved support for Elasticsearch sub-types in AST for both validation and autocomplete (#188600)"

This reverts commit d9282a6e6e.
This commit is contained in:
Jonathan Budzenski 2024-07-31 18:37:05 -05:00
parent d9e1223f28
commit 8a60cd4e68
37 changed files with 14365 additions and 30691 deletions

View file

@ -205,7 +205,7 @@ export class AstListener implements ESQLParserListener {
const command = createCommand('limit', ctx);
this.ast.push(command);
if (ctx.getToken(esql_parser.INTEGER_LITERAL, 0)) {
const literal = createLiteral('integer', ctx.INTEGER_LITERAL());
const literal = createLiteral('number', ctx.INTEGER_LITERAL());
if (literal) {
command.args.push(literal);
}

View file

@ -35,9 +35,7 @@ import type {
ESQLCommandMode,
ESQLInlineCast,
ESQLUnknownItem,
ESQLNumericLiteralType,
FunctionSubtype,
ESQLNumericLiteral,
} from './types';
export function nonNullable<T>(v: T): v is NonNullable<T> {
@ -89,14 +87,11 @@ export function createList(ctx: ParserRuleContext, values: ESQLLiteral[]): ESQLL
};
}
export function createNumericLiteral(
ctx: DecimalValueContext | IntegerValueContext,
literalType: ESQLNumericLiteralType
): ESQLLiteral {
export function createNumericLiteral(ctx: DecimalValueContext | IntegerValueContext): ESQLLiteral {
const text = ctx.getText();
return {
type: 'literal',
literalType,
literalType: 'number',
text,
name: text,
value: Number(text),
@ -105,13 +100,10 @@ export function createNumericLiteral(
};
}
export function createFakeMultiplyLiteral(
ctx: ArithmeticUnaryContext,
literalType: ESQLNumericLiteralType
): ESQLLiteral {
export function createFakeMultiplyLiteral(ctx: ArithmeticUnaryContext): ESQLLiteral {
return {
type: 'literal',
literalType,
literalType: 'number',
text: ctx.getText(),
name: ctx.getText(),
value: ctx.PLUS() ? 1 : -1,
@ -166,13 +158,12 @@ export function createLiteral(
location: getPosition(node.symbol),
incomplete: isMissingText(text),
};
if (type === 'decimal' || type === 'integer') {
if (type === 'number') {
return {
...partialLiteral,
literalType: type,
value: Number(text),
paramType: 'number',
} as ESQLNumericLiteral<'decimal'> | ESQLNumericLiteral<'integer'>;
};
} else if (type === 'param') {
throw new Error('Should never happen');
}
@ -180,7 +171,7 @@ export function createLiteral(
...partialLiteral,
literalType: type,
value: text,
} as ESQLLiteral;
};
}
export function createTimeUnit(ctx: QualifiedIntegerLiteralContext): ESQLTimeInterval {

View file

@ -84,7 +84,7 @@ import {
createUnknownItem,
} from './ast_helpers';
import { getPosition } from './ast_position_utils';
import {
import type {
ESQLLiteral,
ESQLColumn,
ESQLFunction,
@ -289,7 +289,7 @@ function visitOperatorExpression(
const arg = visitOperatorExpression(ctx.operatorExpression());
// this is a number sign thing
const fn = createFunction('*', ctx, undefined, 'binary-expression');
fn.args.push(createFakeMultiplyLiteral(ctx, 'integer'));
fn.args.push(createFakeMultiplyLiteral(ctx));
if (arg) {
fn.args.push(arg);
}
@ -328,21 +328,16 @@ function getConstant(ctx: ConstantContext): ESQLAstItem {
// e.g. 1 year, 15 months
return createTimeUnit(ctx);
}
// Decimal type covers multiple ES|QL types: long, double, etc.
if (ctx instanceof DecimalLiteralContext) {
return createNumericLiteral(ctx.decimalValue(), 'decimal');
return createNumericLiteral(ctx.decimalValue());
}
// Integer type encompasses integer
if (ctx instanceof IntegerLiteralContext) {
return createNumericLiteral(ctx.integerValue(), 'integer');
return createNumericLiteral(ctx.integerValue());
}
if (ctx instanceof BooleanLiteralContext) {
return getBooleanValue(ctx);
}
if (ctx instanceof StringLiteralContext) {
// String literal covers multiple ES|QL types: text and keyword types
return createLiteral('string', ctx.string_().QUOTED_STRING());
}
if (
@ -351,18 +346,14 @@ function getConstant(ctx: ConstantContext): ESQLAstItem {
ctx instanceof StringArrayLiteralContext
) {
const values: ESQLLiteral[] = [];
for (const numericValue of ctx.getTypedRuleContexts(NumericValueContext)) {
const isDecimal =
numericValue.decimalValue() !== null && numericValue.decimalValue() !== undefined;
const value = numericValue.decimalValue() || numericValue.integerValue();
values.push(createNumericLiteral(value!, isDecimal ? 'decimal' : 'integer'));
values.push(createNumericLiteral(value!));
}
for (const booleanValue of ctx.getTypedRuleContexts(BooleanValueContext)) {
values.push(getBooleanValue(booleanValue)!);
}
for (const string of ctx.getTypedRuleContexts(StringContext)) {
// String literal covers multiple ES|QL types: text and keyword types
const literal = createLiteral('string', string.QUOTED_STRING());
if (literal) {
values.push(literal);

View file

@ -179,30 +179,19 @@ export interface ESQLList extends ESQLAstBaseItem {
values: ESQLLiteral[];
}
export type ESQLNumericLiteralType = 'decimal' | 'integer';
export type ESQLLiteral =
| ESQLDecimalLiteral
| ESQLIntegerLiteral
| ESQLNumberLiteral
| ESQLBooleanLiteral
| ESQLNullLiteral
| ESQLStringLiteral
| ESQLParamLiteral<string>;
// Exporting here to prevent TypeScript error TS4058
// Return type of exported function has or is using name 'ESQLNumericLiteral' from external module
// @internal
export interface ESQLNumericLiteral<T extends ESQLNumericLiteralType> extends ESQLAstBaseItem {
export interface ESQLNumberLiteral extends ESQLAstBaseItem {
type: 'literal';
literalType: T;
literalType: 'number';
value: number;
}
// We cast anything as decimal (e.g. 32.12) as generic decimal numeric type here
// @internal
export type ESQLDecimalLiteral = ESQLNumericLiteral<'decimal'>;
// @internal
export type ESQLIntegerLiteral = ESQLNumericLiteral<'integer'>;
// @internal
export interface ESQLBooleanLiteral extends ESQLAstBaseItem {

View file

@ -211,7 +211,7 @@ describe('structurally can walk all nodes', () => {
expect(columns).toMatchObject([
{
type: 'literal',
literalType: 'integer',
literalType: 'number',
name: '123',
},
{
@ -244,7 +244,7 @@ describe('structurally can walk all nodes', () => {
expect(columns).toMatchObject([
{
type: 'literal',
literalType: 'integer',
literalType: 'number',
name: '1',
},
{
@ -264,7 +264,7 @@ describe('structurally can walk all nodes', () => {
},
{
type: 'literal',
literalType: 'decimal',
literalType: 'number',
name: '3.14',
},
]);
@ -288,12 +288,12 @@ describe('structurally can walk all nodes', () => {
values: [
{
type: 'literal',
literalType: 'integer',
literalType: 'number',
name: '1',
},
{
type: 'literal',
literalType: 'integer',
literalType: 'number',
name: '2',
},
],
@ -318,12 +318,12 @@ describe('structurally can walk all nodes', () => {
values: [
{
type: 'literal',
literalType: 'integer',
literalType: 'number',
name: '1',
},
{
type: 'literal',
literalType: 'integer',
literalType: 'number',
name: '2',
},
],
@ -333,7 +333,7 @@ describe('structurally can walk all nodes', () => {
values: [
{
type: 'literal',
literalType: 'decimal',
literalType: 'number',
name: '3.3',
},
],
@ -342,17 +342,17 @@ describe('structurally can walk all nodes', () => {
expect(literals).toMatchObject([
{
type: 'literal',
literalType: 'integer',
literalType: 'number',
name: '1',
},
{
type: 'literal',
literalType: 'integer',
literalType: 'number',
name: '2',
},
{
type: 'literal',
literalType: 'decimal',
literalType: 'number',
name: '3.3',
},
]);
@ -511,7 +511,7 @@ describe('structurally can walk all nodes', () => {
describe('cast expression', () => {
test('can visit cast expression', () => {
const query = 'FROM index | STATS a = 123::integer';
const query = 'FROM index | STATS a = 123::number';
const { ast } = getAstAndSyntaxErrors(query);
const casts: ESQLInlineCast[] = [];
@ -523,10 +523,10 @@ describe('structurally can walk all nodes', () => {
expect(casts).toMatchObject([
{
type: 'inlineCast',
castType: 'integer',
castType: 'number',
value: {
type: 'literal',
literalType: 'integer',
literalType: 'number',
value: 123,
},
},

View file

@ -12,6 +12,7 @@ import { join } from 'path';
import _ from 'lodash';
import type { RecursivePartial } from '@kbn/utility-types';
import { FunctionDefinition } from '../src/definitions/types';
import { esqlToKibanaType } from '../src/shared/esql_to_kibana_type';
const aliasTable: Record<string, string[]> = {
to_version: ['to_ver'],
@ -239,10 +240,10 @@ function getFunctionDefinition(ESFunctionDefinition: Record<string, any>): Funct
...signature,
params: signature.params.map((param: any) => ({
...param,
type: param.type,
type: esqlToKibanaType(param.type),
description: undefined,
})),
returnType: signature.returnType,
returnType: esqlToKibanaType(signature.returnType),
variadic: undefined, // we don't support variadic property
minParams: signature.variadic
? signature.params.filter((param: any) => !param.optional).length

View file

@ -25,7 +25,6 @@ import {
} from '../src/definitions/types';
import { FUNCTION_DESCRIBE_BLOCK_NAME } from '../src/validation/function_describe_block_name';
import { getMaxMinNumberOfParams } from '../src/validation/helpers';
import { ESQL_NUMBER_TYPES, isNumericType, isStringType } from '../src/shared/esql_types';
export const fieldNameFromType = (type: SupportedFieldType) => `${camelCase(type)}Field`;
@ -142,8 +141,8 @@ function generateImplicitDateCastingTestsForFunction(
const allSignaturesWithDateParams = definition.signatures.filter((signature) =>
signature.params.some(
(param, i) =>
(param.type === 'date' || param.type === 'date_period') &&
!definition.signatures.some((def) => isStringType(getParamAtPosition(def, i)?.type)) // don't count parameters that already accept a string
param.type === 'date' &&
!definition.signatures.some((def) => getParamAtPosition(def, i)?.type === 'string') // don't count parameters that already accept a string
)
);
@ -301,8 +300,8 @@ function generateWhereCommandTestsForEvalFunction(
// TODO: not sure why there's this constraint...
const supportedFunction = signatures.some(
({ returnType, params }) =>
[...ESQL_NUMBER_TYPES, 'string'].includes(returnType) &&
params.every(({ type }) => [...ESQL_NUMBER_TYPES, 'string'].includes(type))
['number', 'string'].includes(returnType) &&
params.every(({ type }) => ['number', 'string'].includes(type))
);
if (!supportedFunction) {
@ -312,12 +311,12 @@ function generateWhereCommandTestsForEvalFunction(
const supportedSignatures = signatures.filter(({ returnType }) =>
// TODO — not sure why the tests have this limitation... seems like any type
// that can be part of a boolean expression should be allowed in a where clause
[...ESQL_NUMBER_TYPES, 'string'].includes(returnType)
['number', 'string'].includes(returnType)
);
for (const { params, returnType, ...restSign } of supportedSignatures) {
const correctMapping = getFieldMapping(params);
testCases.set(
`from a_index | where ${!isNumericType(returnType) ? 'length(' : ''}${
`from a_index | where ${returnType !== 'number' ? 'length(' : ''}${
// hijacking a bit this function to produce a function call
getFunctionSignatures(
{
@ -327,7 +326,7 @@ function generateWhereCommandTestsForEvalFunction(
},
{ withTypes: false }
)[0].declaration
}${!isNumericType(returnType) ? ')' : ''} > 0`,
}${returnType !== 'number' ? ')' : ''} > 0`,
[]
);
@ -338,7 +337,7 @@ function generateWhereCommandTestsForEvalFunction(
supportedTypesAndFieldNames
);
testCases.set(
`from a_index | where ${!isNumericType(returnType) ? 'length(' : ''}${
`from a_index | where ${returnType !== 'number' ? 'length(' : ''}${
// hijacking a bit this function to produce a function call
getFunctionSignatures(
{
@ -348,7 +347,7 @@ function generateWhereCommandTestsForEvalFunction(
},
{ withTypes: false }
)[0].declaration
}${!isNumericType(returnType) ? ')' : ''} > 0`,
}${returnType !== 'number' ? ')' : ''} > 0`,
expectedErrors
);
}
@ -358,7 +357,7 @@ function generateWhereCommandTestsForAggFunction(
{ name, alias, signatures, ...defRest }: FunctionDefinition,
testCases: Map<string, string[]>
) {
// statsSignatures.some(({ returnType, params }) => [...ESQL_NUMBER_TYPES].includes(returnType))
// statsSignatures.some(({ returnType, params }) => ['number'].includes(returnType))
for (const { params, ...signRest } of signatures) {
const fieldMapping = getFieldMapping(params);
@ -543,7 +542,7 @@ function generateEvalCommandTestsForEvalFunction(
signatureWithGreatestNumberOfParams.params
).concat({
name: 'extraArg',
type: 'integer',
type: 'number',
});
// get the expected args from the first signature in case of errors
@ -661,7 +660,7 @@ function generateStatsCommandTestsForAggFunction(
testCases.set(`from a_index | stats var = ${correctSignature}`, []);
testCases.set(`from a_index | stats ${correctSignature}`, []);
if (isNumericType(signRest.returnType)) {
if (signRest.returnType === 'number') {
testCases.set(`from a_index | stats var = round(${correctSignature})`, []);
testCases.set(`from a_index | stats round(${correctSignature})`, []);
testCases.set(
@ -714,8 +713,8 @@ function generateStatsCommandTestsForAggFunction(
}
// test only numeric functions for now
if (isNumericType(params[0].type)) {
const nestedBuiltin = 'doubleField / 2';
if (params[0].type === 'number') {
const nestedBuiltin = 'numberField / 2';
const fieldMappingWithNestedBuiltinFunctions = getFieldMapping(params);
fieldMappingWithNestedBuiltinFunctions[0].name = nestedBuiltin;
@ -727,16 +726,16 @@ function generateStatsCommandTestsForAggFunction(
},
{ withTypes: false }
)[0].declaration;
// from a_index | STATS aggFn( doubleField / 2 )
// from a_index | STATS aggFn( numberField / 2 )
testCases.set(`from a_index | stats ${fnSignatureWithBuiltinString}`, []);
testCases.set(`from a_index | stats var0 = ${fnSignatureWithBuiltinString}`, []);
testCases.set(`from a_index | stats avg(doubleField), ${fnSignatureWithBuiltinString}`, []);
testCases.set(`from a_index | stats avg(numberField), ${fnSignatureWithBuiltinString}`, []);
testCases.set(
`from a_index | stats avg(doubleField), var0 = ${fnSignatureWithBuiltinString}`,
`from a_index | stats avg(numberField), var0 = ${fnSignatureWithBuiltinString}`,
[]
);
const nestedEvalAndBuiltin = 'round(doubleField / 2)';
const nestedEvalAndBuiltin = 'round(numberField / 2)';
const fieldMappingWithNestedEvalAndBuiltinFunctions = getFieldMapping(params);
fieldMappingWithNestedBuiltinFunctions[0].name = nestedEvalAndBuiltin;
@ -748,18 +747,18 @@ function generateStatsCommandTestsForAggFunction(
},
{ withTypes: false }
)[0].declaration;
// from a_index | STATS aggFn( round(doubleField / 2) )
// from a_index | STATS aggFn( round(numberField / 2) )
testCases.set(`from a_index | stats ${fnSignatureWithEvalAndBuiltinString}`, []);
testCases.set(`from a_index | stats var0 = ${fnSignatureWithEvalAndBuiltinString}`, []);
testCases.set(
`from a_index | stats avg(doubleField), ${fnSignatureWithEvalAndBuiltinString}`,
`from a_index | stats avg(numberField), ${fnSignatureWithEvalAndBuiltinString}`,
[]
);
testCases.set(
`from a_index | stats avg(doubleField), var0 = ${fnSignatureWithEvalAndBuiltinString}`,
`from a_index | stats avg(numberField), var0 = ${fnSignatureWithEvalAndBuiltinString}`,
[]
);
// from a_index | STATS aggFn(round(doubleField / 2) ) BY round(doubleField / 2)
// from a_index | STATS aggFn(round(numberField / 2) ) BY round(numberField / 2)
testCases.set(
`from a_index | stats ${fnSignatureWithEvalAndBuiltinString} by ${nestedEvalAndBuiltin}`,
[]
@ -769,19 +768,19 @@ function generateStatsCommandTestsForAggFunction(
[]
);
testCases.set(
`from a_index | stats avg(doubleField), ${fnSignatureWithEvalAndBuiltinString} by ${nestedEvalAndBuiltin}, ipField`,
`from a_index | stats avg(numberField), ${fnSignatureWithEvalAndBuiltinString} by ${nestedEvalAndBuiltin}, ipField`,
[]
);
testCases.set(
`from a_index | stats avg(doubleField), var0 = ${fnSignatureWithEvalAndBuiltinString} by var1 = ${nestedEvalAndBuiltin}, ipField`,
`from a_index | stats avg(numberField), var0 = ${fnSignatureWithEvalAndBuiltinString} by var1 = ${nestedEvalAndBuiltin}, ipField`,
[]
);
testCases.set(
`from a_index | stats avg(doubleField), ${fnSignatureWithEvalAndBuiltinString} by ${nestedEvalAndBuiltin}, ${nestedBuiltin}`,
`from a_index | stats avg(numberField), ${fnSignatureWithEvalAndBuiltinString} by ${nestedEvalAndBuiltin}, ${nestedBuiltin}`,
[]
);
testCases.set(
`from a_index | stats avg(doubleField), var0 = ${fnSignatureWithEvalAndBuiltinString} by var1 = ${nestedEvalAndBuiltin}, ${nestedBuiltin}`,
`from a_index | stats avg(numberField), var0 = ${fnSignatureWithEvalAndBuiltinString} by var1 = ${nestedEvalAndBuiltin}, ${nestedBuiltin}`,
[]
);
}
@ -799,7 +798,7 @@ function generateStatsCommandTestsForAggFunction(
.filter(({ constantOnly }) => !constantOnly)
.map(
(_) =>
`Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(doubleField)] of type [double]`
`Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]`
);
testCases.set(
`from a_index | stats var = ${
@ -966,17 +965,9 @@ function generateSortCommandTestsForAggFunction(
const generateSortCommandTestsForGroupingFunction = generateSortCommandTestsForAggFunction;
const fieldTypesToConstants: Record<SupportedFieldType, string> = {
text: '"a"',
keyword: '"a"',
double: '5.5',
integer: '5',
long: '5',
unsigned_long: '5',
counter_integer: '5',
counter_long: '5',
counter_double: '5.5',
date: 'to_datetime("2021-01-01T00:00:00Z")',
date_period: 'to_date_period("2021-01-01/2021-01-02")',
string: '"a"',
number: '5',
date: 'now()',
boolean: 'true',
version: 'to_version("1.0.0")',
ip: 'to_ip("127.0.0.1")',
@ -1012,8 +1003,8 @@ function prepareNestedFunction(fnSignature: FunctionDefinition): string {
}
const toAvgSignature = statsAggregationFunctionDefinitions.find(({ name }) => name === 'avg')!;
const toInteger = evalFunctionDefinitions.find(({ name }) => name === 'to_integer')!;
const toDoubleSignature = evalFunctionDefinitions.find(({ name }) => name === 'to_double')!;
const toStringSignature = evalFunctionDefinitions.find(({ name }) => name === 'to_string')!;
const toDateSignature = evalFunctionDefinitions.find(({ name }) => name === 'to_datetime')!;
const toBooleanSignature = evalFunctionDefinitions.find(({ name }) => name === 'to_boolean')!;
@ -1028,12 +1019,10 @@ const toCartesianShapeSignature = evalFunctionDefinitions.find(
)!;
const toVersionSignature = evalFunctionDefinitions.find(({ name }) => name === 'to_version')!;
// We don't have full list for long, unsigned_long, etc.
const nestedFunctions: Record<SupportedFieldType, string> = {
double: prepareNestedFunction(toDoubleSignature),
integer: prepareNestedFunction(toInteger),
text: prepareNestedFunction(toStringSignature),
keyword: prepareNestedFunction(toStringSignature),
number: prepareNestedFunction(toInteger),
string: prepareNestedFunction(toStringSignature),
date: prepareNestedFunction(toDateSignature),
boolean: prepareNestedFunction(toBooleanSignature),
ip: prepareNestedFunction(toIpSignature),
version: prepareNestedFunction(toVersionSignature),
@ -1041,8 +1030,6 @@ const nestedFunctions: Record<SupportedFieldType, string> = {
geo_shape: prepareNestedFunction(toGeoShapeSignature),
cartesian_point: prepareNestedFunction(toCartesianPointSignature),
cartesian_shape: prepareNestedFunction(toCartesianShapeSignature),
// @ts-expect-error
datetime: prepareNestedFunction(toDateSignature),
};
function getFieldName(
@ -1099,7 +1086,6 @@ function getFieldMapping(
number: '5',
date: 'now()',
};
return params.map(({ name: _name, type, constantOnly, literalOptions, ...rest }) => {
const typeString: string = type;
if (isSupportedFieldType(typeString)) {
@ -1138,7 +1124,7 @@ function getFieldMapping(
...rest,
};
}
return { name: 'textField', type, ...rest };
return { name: 'stringField', type, ...rest };
});
}
@ -1239,12 +1225,8 @@ function generateIncorrectlyTypedParameters(
}
const fieldName = wrongFieldMapping[i].name;
if (
fieldName === 'doubleField' &&
signatures.every(
(signature) =>
getParamAtPosition(signature, i)?.type !== 'keyword' ||
getParamAtPosition(signature, i)?.type !== 'text'
)
fieldName === 'numberField' &&
signatures.every((signature) => getParamAtPosition(signature, i)?.type !== 'string')
) {
return;
}

View file

@ -11,14 +11,14 @@ import { supportedFieldTypes } from '../definitions/types';
export const fields = [
...supportedFieldTypes.map((type) => ({ name: `${camelCase(type)}Field`, type })),
{ name: 'any#Char$Field', type: 'double' },
{ name: 'kubernetes.something.something', type: 'double' },
{ name: 'any#Char$Field', type: 'number' },
{ name: 'kubernetes.something.something', type: 'number' },
{ name: '@timestamp', type: 'date' },
];
export const enrichFields = [
{ name: 'otherField', type: 'text' },
{ name: 'yetAnotherField', type: 'double' },
{ name: 'otherField', type: 'string' },
{ name: 'yetAnotherField', type: 'number' },
];
// eslint-disable-next-line @typescript-eslint/naming-convention
@ -58,7 +58,7 @@ export function getCallbackMocks() {
return unsupported_field;
}
if (/dissect|grok/.test(query)) {
return [{ name: 'firstWord', type: 'text' }];
return [{ name: 'firstWord', type: 'string' }];
}
return fields;
}),

View file

@ -6,11 +6,8 @@
* Side Public License, v 1.
*/
import { ESQL_COMMON_NUMERIC_TYPES, ESQL_NUMBER_TYPES } from '../../shared/esql_types';
import { setup, getFunctionSignaturesByReturnType, getFieldNamesByType } from './helpers';
const ESQL_NUMERIC_TYPES = ESQL_NUMBER_TYPES as unknown as string[];
const allAggFunctions = getFunctionSignaturesByReturnType('stats', 'any', {
agg: true,
});
@ -77,76 +74,51 @@ describe('autocomplete.suggest', () => {
const { assertSuggestions } = await setup();
await assertSuggestions('from a | stats by bucket(/', [
...getFieldNamesByType([...ESQL_COMMON_NUMERIC_TYPES, 'date']).map(
(field) => `${field},`
...getFieldNamesByType(['number', 'date']).map((field) => `${field},`),
...getFunctionSignaturesByReturnType('eval', ['date', 'number'], { scalar: true }).map(
(s) => ({ ...s, text: `${s.text},` })
),
...getFunctionSignaturesByReturnType('eval', ['date', ...ESQL_COMMON_NUMERIC_TYPES], {
scalar: true,
}).map((s) => ({ ...s, text: `${s.text},` })),
]);
await assertSuggestions('from a | stats round(/', [
...getFunctionSignaturesByReturnType('stats', ESQL_NUMERIC_TYPES, {
agg: true,
grouping: true,
}),
...getFieldNamesByType(ESQL_NUMERIC_TYPES),
...getFunctionSignaturesByReturnType(
'eval',
ESQL_NUMERIC_TYPES,
{ scalar: true },
undefined,
['round']
),
...getFunctionSignaturesByReturnType('stats', 'number', { agg: true, grouping: true }),
...getFieldNamesByType('number'),
...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }, undefined, [
'round',
]),
]);
await assertSuggestions('from a | stats round(round(/', [
...getFunctionSignaturesByReturnType('stats', ESQL_NUMERIC_TYPES, { agg: true }),
...getFieldNamesByType(ESQL_NUMERIC_TYPES),
...getFunctionSignaturesByReturnType(
'eval',
ESQL_NUMERIC_TYPES,
{ scalar: true },
undefined,
['round']
),
...getFunctionSignaturesByReturnType('stats', 'number', { agg: true }),
...getFieldNamesByType('number'),
...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }, undefined, [
'round',
]),
]);
await assertSuggestions('from a | stats avg(round(/', [
...getFieldNamesByType(ESQL_NUMERIC_TYPES),
...getFunctionSignaturesByReturnType(
'eval',
ESQL_NUMERIC_TYPES,
{ scalar: true },
undefined,
['round']
),
...getFieldNamesByType('number'),
...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }, undefined, [
'round',
]),
]);
await assertSuggestions('from a | stats avg(/', [
...getFieldNamesByType(ESQL_NUMERIC_TYPES),
...getFunctionSignaturesByReturnType('eval', ESQL_NUMERIC_TYPES, { scalar: true }),
...getFieldNamesByType('number'),
...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }),
]);
await assertSuggestions('from a | stats round(avg(/', [
...getFieldNamesByType(ESQL_NUMERIC_TYPES),
...getFunctionSignaturesByReturnType(
'eval',
ESQL_NUMERIC_TYPES,
{ scalar: true },
undefined,
['round']
),
...getFieldNamesByType('number'),
...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }, undefined, [
'round',
]),
]);
});
test('when typing inside function left paren', async () => {
const { assertSuggestions } = await setup();
const expected = [
...getFieldNamesByType([...ESQL_COMMON_NUMERIC_TYPES, 'date', 'boolean', 'ip']),
...getFunctionSignaturesByReturnType(
'stats',
[...ESQL_COMMON_NUMERIC_TYPES, 'date', 'boolean', 'ip'],
{
scalar: true,
}
),
...getFieldNamesByType(['number', 'date', 'boolean', 'ip']),
...getFunctionSignaturesByReturnType('stats', ['number', 'date', 'boolean', 'ip'], {
scalar: true,
}),
];
await assertSuggestions('from a | stats a=min(/)', expected);
@ -158,14 +130,8 @@ describe('autocomplete.suggest', () => {
const { assertSuggestions } = await setup();
await assertSuggestions('from a | stats avg(b/) by stringField', [
...getFieldNamesByType('double'),
...getFunctionSignaturesByReturnType(
'eval',
['double', 'integer', 'long', 'unsigned_long'],
{
scalar: true,
}
),
...getFieldNamesByType('number'),
...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }),
]);
});
@ -239,15 +205,10 @@ describe('autocomplete.suggest', () => {
test('on space before expression right hand side operand', async () => {
const { assertSuggestions } = await setup();
await assertSuggestions('from a | stats avg(b) by integerField % /', [
...getFieldNamesByType('integer'),
...getFieldNamesByType('double'),
...getFieldNamesByType('long'),
await assertSuggestions('from a | stats avg(b) by numberField % /', [
...getFieldNamesByType('number'),
'`avg(b)`',
...getFunctionSignaturesByReturnType('eval', ['integer', 'double', 'long'], {
scalar: true,
}),
...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }),
...allGroupingFunctions,
]);
await assertSuggestions('from a | stats avg(b) by var0 = /', [
@ -265,10 +226,10 @@ describe('autocomplete.suggest', () => {
test('on space after expression right hand side operand', async () => {
const { assertSuggestions } = await setup();
await assertSuggestions('from a | stats avg(b) by doubleField % 2 /', [',', '|']);
await assertSuggestions('from a | stats avg(b) by numberField % 2 /', [',', '|']);
await assertSuggestions(
'from a | stats var0 = AVG(doubleField) BY var1 = BUCKET(dateField, 1 day)/',
'from a | stats var0 = AVG(products.base_price) BY var1 = BUCKET(order_date, 1 day)/',
[',', '|', '+ $0', '- $0']
);
});

View file

@ -41,7 +41,7 @@ export const triggerCharacters = [',', '(', '=', ' '];
export const fields: Array<{ name: string; type: string; suggestedAs?: string }> = [
...[
'string',
'double',
'number',
'date',
'boolean',
'ip',
@ -53,8 +53,8 @@ export const fields: Array<{ name: string; type: string; suggestedAs?: string }>
name: `${camelCase(type)}Field`,
type,
})),
{ name: 'any#Char$Field', type: 'double', suggestedAs: '`any#Char$Field`' },
{ name: 'kubernetes.something.something', type: 'double' },
{ name: 'any#Char$Field', type: 'number', suggestedAs: '`any#Char$Field`' },
{ name: 'kubernetes.something.something', type: 'number' },
];
export const indexes = (

View file

@ -10,10 +10,15 @@ import { suggest } from './autocomplete';
import { evalFunctionDefinitions } from '../definitions/functions';
import { timeUnitsToSuggest } from '../definitions/literals';
import { commandDefinitions } from '../definitions/commands';
import { getSafeInsertText, getUnitDuration, TRIGGER_SUGGESTION_COMMAND } from './factories';
import {
getSafeInsertText,
getUnitDuration,
TRIGGER_SUGGESTION_COMMAND,
TIME_SYSTEM_PARAMS,
} from './factories';
import { camelCase, partition } from 'lodash';
import { getAstAndSyntaxErrors } from '@kbn/esql-ast';
import { FunctionParameter, FunctionReturnType } from '../definitions/types';
import { FunctionParameter } from '../definitions/types';
import { getParamAtPosition } from './helper';
import { nonNullable } from '../shared/helpers';
import {
@ -26,16 +31,9 @@ import {
createCompletionContext,
getPolicyFields,
PartialSuggestionWithText,
TIME_PICKER_SUGGESTION,
} from './__tests__/helpers';
import { METADATA_FIELDS } from '../shared/constants';
import {
ESQL_COMMON_NUMERIC_TYPES as UNCASTED_ESQL_COMMON_NUMERIC_TYPES,
ESQL_NUMBER_TYPES,
} from '../shared/esql_types';
const ESQL_NUMERIC_TYPES = ESQL_NUMBER_TYPES as unknown as string[];
const ESQL_COMMON_NUMERIC_TYPES =
UNCASTED_ESQL_COMMON_NUMERIC_TYPES as unknown as FunctionReturnType[];
describe('autocomplete', () => {
type TestArgs = [
@ -168,18 +166,25 @@ describe('autocomplete', () => {
['string']
),
]);
testSuggestions('from a | where textField >= ', [
...getFieldNamesByType('any'),
...getFunctionSignaturesByReturnType('where', ['any'], { scalar: true }),
testSuggestions('from a | where stringField >= ', [
...getFieldNamesByType('string'),
...getFunctionSignaturesByReturnType('where', 'string', { scalar: true }),
]);
// Skip these tests until the insensitive case equality gets restored back
testSuggestions.skip('from a | where stringField =~ ', [
...getFieldNamesByType('string'),
...getFunctionSignaturesByReturnType('where', 'string', { scalar: true }),
]);
testSuggestions('from a | where textField >= textField', [
...getFieldNamesByType('any'),
...getFunctionSignaturesByReturnType('where', 'any', { scalar: true }),
testSuggestions('from a | where stringField >= stringField ', [
'|',
...getFunctionSignaturesByReturnType(
'where',
'boolean',
{
builtin: true,
},
['boolean']
),
]);
testSuggestions.skip('from a | where stringField =~ stringField ', [
'|',
@ -197,60 +202,52 @@ describe('autocomplete', () => {
...getFieldNamesByType('any'),
...getFunctionSignaturesByReturnType('where', 'any', { scalar: true }),
]);
testSuggestions(`from a | where stringField >= stringField ${op} doubleField `, [
...getFunctionSignaturesByReturnType('where', 'boolean', { builtin: true }, ['double']),
testSuggestions(`from a | where stringField >= stringField ${op} numberField `, [
...getFunctionSignaturesByReturnType('where', 'boolean', { builtin: true }, ['number']),
]);
testSuggestions(`from a | where stringField >= stringField ${op} doubleField == `, [
...getFieldNamesByType(ESQL_NUMERIC_TYPES),
...getFunctionSignaturesByReturnType('where', ESQL_COMMON_NUMERIC_TYPES, { scalar: true }),
testSuggestions(`from a | where stringField >= stringField ${op} numberField == `, [
...getFieldNamesByType('number'),
...getFunctionSignaturesByReturnType('where', 'number', { scalar: true }),
]);
}
testSuggestions('from a | stats a=avg(doubleField) | where a ', [
testSuggestions('from a | stats a=avg(numberField) | where a ', [
...getFunctionSignaturesByReturnType('where', 'any', { builtin: true, skipAssign: true }, [
'double',
'number',
]),
]);
// Mind this test: suggestion is aware of previous commands when checking for fields
// in this case the doubleField has been wiped by the STATS command and suggest cannot find it's type
// in this case the numberField has been wiped by the STATS command and suggest cannot find it's type
// @TODO: verify this is the correct behaviour in this case or if we want a "generic" suggestion anyway
testSuggestions(
'from a | stats a=avg(doubleField) | where doubleField ',
'from a | stats a=avg(numberField) | where numberField ',
[],
undefined,
undefined,
// make the fields suggest aware of the previous STATS, leave the other callbacks untouched
[[{ name: 'a', type: 'double' }], undefined, undefined]
[[{ name: 'a', type: 'number' }], undefined, undefined]
);
// The editor automatically inject the final bracket, so it is not useful to test with just open bracket
testSuggestions(
'from a | where log10()',
[
...getFieldNamesByType(ESQL_NUMERIC_TYPES),
...getFunctionSignaturesByReturnType(
'where',
ESQL_NUMERIC_TYPES,
{ scalar: true },
undefined,
['log10']
),
...getFieldNamesByType('number'),
...getFunctionSignaturesByReturnType('where', 'number', { scalar: true }, undefined, [
'log10',
]),
],
'('
);
testSuggestions('from a | where log10(doubleField) ', [
...getFunctionSignaturesByReturnType('where', 'double', { builtin: true }, ['double']),
...getFunctionSignaturesByReturnType('where', 'boolean', { builtin: true }, ['double']),
testSuggestions('from a | where log10(numberField) ', [
...getFunctionSignaturesByReturnType('where', 'number', { builtin: true }, ['number']),
...getFunctionSignaturesByReturnType('where', 'boolean', { builtin: true }, ['number']),
]);
testSuggestions(
'from a | WHERE pow(doubleField, )',
'from a | WHERE pow(numberField, )',
[
...getFieldNamesByType(ESQL_NUMERIC_TYPES),
...getFunctionSignaturesByReturnType(
'where',
ESQL_NUMERIC_TYPES,
{ scalar: true },
undefined,
['pow']
),
...getFieldNamesByType('number'),
...getFunctionSignaturesByReturnType('where', 'number', { scalar: true }, undefined, [
'pow',
]),
],
','
);
@ -261,34 +258,34 @@ describe('autocomplete', () => {
...getFieldNamesByType('boolean'),
...getFunctionSignaturesByReturnType('eval', 'boolean', { scalar: true }),
]);
testSuggestions('from index | WHERE doubleField in ', ['( $0 )']);
testSuggestions('from index | WHERE doubleField not in ', ['( $0 )']);
testSuggestions('from index | WHERE numberField in ', ['( $0 )']);
testSuggestions('from index | WHERE numberField not in ', ['( $0 )']);
testSuggestions(
'from index | WHERE doubleField not in ( )',
'from index | WHERE numberField not in ( )',
[
...getFieldNamesByType('double').filter((name) => name !== 'doubleField'),
...getFunctionSignaturesByReturnType('where', 'double', { scalar: true }),
...getFieldNamesByType('number').filter((name) => name !== 'numberField'),
...getFunctionSignaturesByReturnType('where', 'number', { scalar: true }),
],
'('
);
testSuggestions(
'from index | WHERE doubleField in ( `any#Char$Field`, )',
'from index | WHERE numberField in ( `any#Char$Field`, )',
[
...getFieldNamesByType('double').filter(
(name) => name !== '`any#Char$Field`' && name !== 'doubleField'
...getFieldNamesByType('number').filter(
(name) => name !== '`any#Char$Field`' && name !== 'numberField'
),
...getFunctionSignaturesByReturnType('where', 'double', { scalar: true }),
...getFunctionSignaturesByReturnType('where', 'number', { scalar: true }),
],
undefined,
54 // after the first suggestions
);
testSuggestions(
'from index | WHERE doubleField not in ( `any#Char$Field`, )',
'from index | WHERE numberField not in ( `any#Char$Field`, )',
[
...getFieldNamesByType('double').filter(
(name) => name !== '`any#Char$Field`' && name !== 'doubleField'
...getFieldNamesByType('number').filter(
(name) => name !== '`any#Char$Field`' && name !== 'numberField'
),
...getFunctionSignaturesByReturnType('where', 'double', { scalar: true }),
...getFunctionSignaturesByReturnType('where', 'number', { scalar: true }),
],
undefined,
58 // after the first suggestions
@ -380,14 +377,14 @@ describe('autocomplete', () => {
);
testSuggestions(
`from a_index | eval round(doubleField) + 1 | eval \`round(doubleField) + 1\` + 1 | eval \`\`\`round(doubleField) + 1\`\` + 1\` + 1 | eval \`\`\`\`\`\`\`round(doubleField) + 1\`\`\`\` + 1\`\` + 1\` + 1 | eval \`\`\`\`\`\`\`\`\`\`\`\`\`\`\`round(doubleField) + 1\`\`\`\`\`\`\`\` + 1\`\`\`\` + 1\`\` + 1\` + 1 | ${command} `,
`from a_index | eval round(numberField) + 1 | eval \`round(numberField) + 1\` + 1 | eval \`\`\`round(numberField) + 1\`\` + 1\` + 1 | eval \`\`\`\`\`\`\`round(numberField) + 1\`\`\`\` + 1\`\` + 1\` + 1 | eval \`\`\`\`\`\`\`\`\`\`\`\`\`\`\`round(numberField) + 1\`\`\`\`\`\`\`\` + 1\`\`\`\` + 1\`\` + 1\` + 1 | ${command} `,
[
...getFieldNamesByType('any'),
'`round(doubleField) + 1`',
'```round(doubleField) + 1`` + 1`',
'```````round(doubleField) + 1```` + 1`` + 1`',
'```````````````round(doubleField) + 1```````` + 1```` + 1`` + 1`',
'```````````````````````````````round(doubleField) + 1```````````````` + 1```````` + 1```` + 1`` + 1`',
'`round(numberField) + 1`',
'```round(numberField) + 1`` + 1`',
'```````round(numberField) + 1```` + 1`` + 1`',
'```````````````round(numberField) + 1```````` + 1```` + 1`` + 1`',
'```````````````````````````````round(numberField) + 1```````````````` + 1```````` + 1```` + 1`` + 1`',
]
);
});
@ -416,7 +413,7 @@ describe('autocomplete', () => {
testSuggestions(`from a ${prevCommand}| enrich policy `, ['ON $0', 'WITH $0', '|']);
testSuggestions(`from a ${prevCommand}| enrich policy on `, [
'stringField',
'doubleField',
'numberField',
'dateField',
'booleanField',
'ipField',
@ -469,25 +466,25 @@ describe('autocomplete', () => {
...getFieldNamesByType('any'),
...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }),
]);
testSuggestions('from a | eval doubleField ', [
testSuggestions('from a | eval numberField ', [
...getFunctionSignaturesByReturnType('eval', 'any', { builtin: true, skipAssign: true }, [
'double',
'number',
]),
',',
'|',
]);
testSuggestions('from index | EVAL stringField not ', ['LIKE $0', 'RLIKE $0', 'IN $0']);
testSuggestions('from index | EVAL stringField NOT ', ['LIKE $0', 'RLIKE $0', 'IN $0']);
testSuggestions('from index | EVAL doubleField in ', ['( $0 )']);
testSuggestions('from index | EVAL numberField in ', ['( $0 )']);
testSuggestions(
'from index | EVAL doubleField in ( )',
'from index | EVAL numberField in ( )',
[
...getFieldNamesByType('double').filter((name) => name !== 'doubleField'),
...getFunctionSignaturesByReturnType('eval', 'double', { scalar: true }),
...getFieldNamesByType('number').filter((name) => name !== 'numberField'),
...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }),
],
'('
);
testSuggestions('from index | EVAL doubleField not in ', ['( $0 )']);
testSuggestions('from index | EVAL numberField not in ', ['( $0 )']);
testSuggestions('from index | EVAL not ', [
...getFieldNamesByType('boolean'),
...getFunctionSignaturesByReturnType('eval', 'boolean', { scalar: true }),
@ -495,10 +492,10 @@ describe('autocomplete', () => {
testSuggestions('from a | eval a=', [
...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }),
]);
testSuggestions('from a | eval a=abs(doubleField), b= ', [
testSuggestions('from a | eval a=abs(numberField), b= ', [
...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }),
]);
testSuggestions('from a | eval a=doubleField, ', [
testSuggestions('from a | eval a=numberField, ', [
'var0 =',
...getFieldNamesByType('any'),
'a',
@ -512,14 +509,10 @@ describe('autocomplete', () => {
testSuggestions(
'from a | eval a=round()',
[
...getFieldNamesByType(ESQL_NUMERIC_TYPES),
...getFunctionSignaturesByReturnType(
'eval',
ESQL_NUMERIC_TYPES,
{ scalar: true },
undefined,
['round']
),
...getFieldNamesByType('number'),
...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }, undefined, [
'round',
]),
],
'('
);
@ -546,59 +539,64 @@ describe('autocomplete', () => {
[],
' '
);
testSuggestions('from a | eval a=round(doubleField) ', [
testSuggestions('from a | eval a=round(numberField) ', [
',',
'|',
...getFunctionSignaturesByReturnType('eval', 'any', { builtin: true, skipAssign: true }, [
'double',
'number',
]),
]);
testSuggestions(
'from a | eval a=round(doubleField, ',
'from a | eval a=round(numberField, ',
[
...getFieldNamesByType('integer'),
...getFunctionSignaturesByReturnType('eval', 'integer', { scalar: true }, undefined, [
...getFieldNamesByType('number'),
...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }, undefined, [
'round',
]),
],
' '
);
testSuggestions(
'from a | eval round(doubleField, ',
'from a | eval round(numberField, ',
[
...getFunctionSignaturesByReturnType('eval', 'integer', { scalar: true }, undefined, [
...getFieldNamesByType('number'),
...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }, undefined, [
'round',
]),
],
' '
);
testSuggestions('from a | eval a=round(doubleField),', [
testSuggestions('from a | eval a=round(numberField),', [
'var0 =',
...getFieldNamesByType('any'),
'a',
...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }),
]);
testSuggestions('from a | eval a=round(doubleField) + ', [
...getFieldNamesByType(ESQL_NUMERIC_TYPES),
...getFunctionSignaturesByReturnType('eval', ESQL_COMMON_NUMERIC_TYPES, { scalar: true }),
testSuggestions('from a | eval a=round(numberField) + ', [
...getFieldNamesByType('number'),
'a', // @TODO remove this
...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }),
]);
testSuggestions('from a | eval a=round(doubleField)+ ', [
...getFieldNamesByType(ESQL_NUMERIC_TYPES),
...getFunctionSignaturesByReturnType('eval', ESQL_COMMON_NUMERIC_TYPES, { scalar: true }),
testSuggestions('from a | eval a=round(numberField)+ ', [
...getFieldNamesByType('number'),
'a', // @TODO remove this
...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }),
]);
testSuggestions('from a | eval a=doubleField+ ', [
...getFieldNamesByType(ESQL_NUMERIC_TYPES),
...getFunctionSignaturesByReturnType('eval', ESQL_COMMON_NUMERIC_TYPES, { scalar: true }),
testSuggestions('from a | eval a=numberField+ ', [
...getFieldNamesByType('number'),
'a', // @TODO remove this
...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }),
]);
testSuggestions('from a | eval a=`any#Char$Field`+ ', [
...getFieldNamesByType(ESQL_NUMERIC_TYPES),
...getFunctionSignaturesByReturnType('eval', ESQL_COMMON_NUMERIC_TYPES, { scalar: true }),
...getFieldNamesByType('number'),
'a', // @TODO remove this
...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }),
]);
testSuggestions(
'from a | stats avg(doubleField) by stringField | eval ',
'from a | stats avg(numberField) by stringField | eval ',
[
'var0 =',
'`avg(doubleField)`',
'`avg(numberField)`',
...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }),
],
' ',
@ -607,32 +605,32 @@ describe('autocomplete', () => {
[[], undefined, undefined]
);
testSuggestions(
'from a | eval abs(doubleField) + 1 | eval ',
'from a | eval abs(numberField) + 1 | eval ',
[
'var0 =',
...getFieldNamesByType('any'),
'`abs(doubleField) + 1`',
'`abs(numberField) + 1`',
...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }),
],
' '
);
testSuggestions(
'from a | stats avg(doubleField) by stringField | eval ',
'from a | stats avg(numberField) by stringField | eval ',
[
'var0 =',
'`avg(doubleField)`',
'`avg(numberField)`',
...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }),
],
' ',
undefined,
// make aware EVAL of the previous STATS command with the buggy field name from expression
[[{ name: 'avg_doubleField_', type: 'double' }], undefined, undefined]
[[{ name: 'avg_numberField_', type: 'number' }], undefined, undefined]
);
testSuggestions(
'from a | stats avg(doubleField), avg(kubernetes.something.something) by stringField | eval ',
'from a | stats avg(numberField), avg(kubernetes.something.something) by stringField | eval ',
[
'var0 =',
'`avg(doubleField)`',
'`avg(numberField)`',
'`avg(kubernetes.something.something)`',
...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }),
],
@ -641,64 +639,48 @@ describe('autocomplete', () => {
// make aware EVAL of the previous STATS command with the buggy field name from expression
[
[
{ name: 'avg_doubleField_', type: 'double' },
{ name: 'avg_kubernetes.something.something_', type: 'double' },
{ name: 'avg_numberField_', type: 'number' },
{ name: 'avg_kubernetes.something.something_', type: 'number' },
],
undefined,
undefined,
]
);
testSuggestions(
'from a | eval a=round(doubleField), b=round()',
'from a | eval a=round(numberField), b=round()',
[
...getFieldNamesByType(ESQL_NUMERIC_TYPES),
...getFunctionSignaturesByReturnType(
'eval',
ESQL_NUMERIC_TYPES,
{ scalar: true },
undefined,
['round']
),
...getFieldNamesByType('number'),
...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }, undefined, [
'round',
]),
],
'('
);
// test that comma is correctly added to the suggestions if minParams is not reached yet
testSuggestions('from a | eval a=concat( ', [
...getFieldNamesByType(['text', 'keyword']).map((v) => `${v},`),
...getFunctionSignaturesByReturnType(
'eval',
['text', 'keyword'],
{ scalar: true },
undefined,
['concat']
).map((v) => ({ ...v, text: `${v.text},` })),
...getFieldNamesByType('string').map((v) => `${v},`),
...getFunctionSignaturesByReturnType('eval', 'string', { scalar: true }, undefined, [
'concat',
]).map((v) => ({ ...v, text: `${v.text},` })),
]);
testSuggestions(
'from a | eval a=concat(textField, ',
'from a | eval a=concat(stringField, ',
[
...getFieldNamesByType(['text', 'keyword']),
...getFunctionSignaturesByReturnType(
'eval',
['text', 'keyword'],
{ scalar: true },
undefined,
['concat']
),
...getFieldNamesByType('string'),
...getFunctionSignaturesByReturnType('eval', 'string', { scalar: true }, undefined, [
'concat',
]),
],
' '
);
// test that the arg type is correct after minParams
testSuggestions(
'from a | eval a=cidr_match(ipField, textField, ',
'from a | eval a=cidr_match(ipField, stringField, ',
[
...getFieldNamesByType('text'),
...getFunctionSignaturesByReturnType(
'eval',
['text', 'keyword'],
{ scalar: true },
undefined,
['cidr_match']
),
...getFieldNamesByType('string'),
...getFunctionSignaturesByReturnType('eval', 'string', { scalar: true }, undefined, [
'cidr_match',
]),
],
' '
);
@ -712,14 +694,10 @@ describe('autocomplete', () => {
testSuggestions(
'from a | eval a=cidr_match(ipField, ',
[
...getFieldNamesByType(['text', 'keyword']),
...getFunctionSignaturesByReturnType(
'eval',
['text', 'keyword'],
{ scalar: true },
undefined,
['cidr_match']
),
...getFieldNamesByType('string'),
...getFunctionSignaturesByReturnType('eval', 'string', { scalar: true }, undefined, [
'cidr_match',
]),
],
' '
);
@ -731,14 +709,10 @@ describe('autocomplete', () => {
testSuggestions(
`from a | eval a=${Array(nesting).fill('round(').join('')}`,
[
...getFieldNamesByType(ESQL_NUMERIC_TYPES),
...getFunctionSignaturesByReturnType(
'eval',
ESQL_NUMERIC_TYPES,
{ scalar: true },
undefined,
['round']
),
...getFieldNamesByType('number'),
...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }, undefined, [
'round',
]),
],
'('
);
@ -746,12 +720,12 @@ describe('autocomplete', () => {
// Smoke testing for suggestions in previous position than the end of the statement
testSuggestions(
'from a | eval var0 = abs(doubleField) | eval abs(var0)',
'from a | eval var0 = abs(numberField) | eval abs(var0)',
[
',',
'|',
...getFunctionSignaturesByReturnType('eval', 'any', { builtin: true, skipAssign: true }, [
'double',
'number',
]),
],
undefined,
@ -760,14 +734,10 @@ describe('autocomplete', () => {
testSuggestions(
'from a | eval var0 = abs(b) | eval abs(var0)',
[
...getFieldNamesByType(ESQL_NUMERIC_TYPES),
...getFunctionSignaturesByReturnType(
'eval',
ESQL_NUMERIC_TYPES,
{ scalar: true },
undefined,
['abs']
),
...getFieldNamesByType('number'),
...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }, undefined, [
'abs',
]),
],
undefined,
26 /* b column in abs */
@ -776,7 +746,7 @@ describe('autocomplete', () => {
// Test suggestions for each possible param, within each signature variation, for each function
for (const fn of evalFunctionDefinitions) {
// skip this fn for the moment as it's quite hard to test
if (!['bucket', 'date_extract', 'date_diff'].includes(fn.name)) {
if (fn.name !== 'bucket') {
for (const signature of fn.signatures) {
signature.params.forEach((param, i) => {
if (i < signature.params.length) {
@ -852,23 +822,6 @@ describe('autocomplete', () => {
});
}
}
// The above test fails cause it expects nested functions like
// DATE_EXTRACT(concat("aligned_day_","of_week_in_month"), date) to also be suggested
// which is actually valid according to func signature
// but currently, our autocomplete only suggests the literal suggestions
if (['date_extract', 'date_diff'].includes(fn.name)) {
const firstParam = fn.signatures[0].params[0];
const suggestedConstants = firstParam?.literalSuggestions || firstParam?.literalOptions;
const requiresMoreArgs = true;
testSuggestions(
`from a | eval ${fn.name}(`,
suggestedConstants?.length
? [...suggestedConstants.map((option) => `"${option}"${requiresMoreArgs ? ',' : ''}`)]
: []
);
}
}
testSuggestions('from a | eval var0 = bucket(@timestamp, ', getUnitDuration(1), ' ');
@ -883,7 +836,7 @@ describe('autocomplete', () => {
',',
'|',
...getFunctionSignaturesByReturnType('eval', 'any', { builtin: true, skipAssign: true }, [
'integer',
'number',
]),
],
' '
@ -895,20 +848,39 @@ describe('autocomplete', () => {
'time_interval',
]),
]);
testSuggestions('from a | eval a = 1 day + 2 ', [',', '|']);
testSuggestions(
'from a | eval a = 1 day + 2 ',
[
...dateSuggestions,
',',
'|',
...getFunctionSignaturesByReturnType('eval', 'any', { builtin: true, skipAssign: true }, [
'number',
]),
],
' '
);
testSuggestions(
'from a | eval 1 day + 2 ',
[
...dateSuggestions,
...getFunctionSignaturesByReturnType('eval', 'any', { builtin: true, skipAssign: true }, [
'integer',
'number',
]),
],
' '
);
testSuggestions(
'from a | eval var0=date_trunc()',
[...getLiteralsByType('time_literal').map((t) => `${t},`)],
[
...[...TIME_SYSTEM_PARAMS].map((t) => `${t},`),
...getLiteralsByType('time_literal').map((t) => `${t},`),
...getFunctionSignaturesByReturnType('eval', 'date', { scalar: true }, undefined, [
'date_trunc',
]).map((t) => ({ ...t, text: `${t.text},` })),
...getFieldNamesByType('date').map((t) => `${t},`),
TIME_PICKER_SUGGESTION,
],
'('
);
testSuggestions(
@ -945,7 +917,7 @@ describe('autocomplete', () => {
describe('callbacks', () => {
it('should send the fields query without the last command', async () => {
const callbackMocks = createCustomCallbackMocks(undefined, undefined, undefined);
const statement = 'from a | drop stringField | eval var0 = abs(doubleField) ';
const statement = 'from a | drop stringField | eval var0 = abs(numberField) ';
const triggerOffset = statement.lastIndexOf(' ');
const context = createCompletionContext(statement[triggerOffset]);
await suggest(
@ -961,7 +933,7 @@ describe('autocomplete', () => {
});
it('should send the fields query aware of the location', async () => {
const callbackMocks = createCustomCallbackMocks(undefined, undefined, undefined);
const statement = 'from a | drop | eval var0 = abs(doubleField) ';
const statement = 'from a | drop | eval var0 = abs(numberField) ';
const triggerOffset = statement.lastIndexOf('p') + 1; // drop <here>
const context = createCompletionContext(statement[triggerOffset]);
await suggest(
@ -1053,13 +1025,10 @@ describe('autocomplete', () => {
testSuggestions(
'FROM kibana_sample_data_logs | EVAL TRIM(e)',
[
...getFunctionSignaturesByReturnType(
'eval',
['text', 'keyword'],
{ scalar: true },
undefined,
['trim']
),
...getFieldNamesByType('string'),
...getFunctionSignaturesByReturnType('eval', 'string', { scalar: true }, undefined, [
'trim',
]),
],
undefined,
42

View file

@ -16,7 +16,6 @@ import type {
ESQLSingleAstItem,
} from '@kbn/esql-ast';
import { partition } from 'lodash';
import { ESQL_NUMBER_TYPES, isNumericType } from '../shared/esql_types';
import type { EditorContext, SuggestionRawDefinition } from './types';
import {
lookupColumn,
@ -89,7 +88,6 @@ import {
getParamAtPosition,
getQueryForFields,
getSourcesFromCommands,
getSupportedTypesForBinaryOperators,
isAggFunctionUsedAlready,
removeQuoteForSuggestedSources,
} from './helper';
@ -126,7 +124,7 @@ function appendEnrichFields(
// @TODO: improve this
const newMap: Map<string, ESQLRealField> = new Map(fieldsMap);
for (const field of policyMetadata.enrichFields) {
newMap.set(field, { name: field, type: 'double' });
newMap.set(field, { name: field, type: 'number' });
}
return newMap;
}
@ -734,7 +732,7 @@ async function getExpressionSuggestionsByType(
workoutBuiltinOptions(rightArg, references)
)
);
if (isNumericType(nodeArgType) && isLiteralItem(rightArg)) {
if (nodeArgType === 'number' && isLiteralItem(rightArg)) {
// ... EVAL var = 1 <suggest>
suggestions.push(...getCompatibleLiterals(command.name, ['time_literal_unit']));
}
@ -742,7 +740,7 @@ async function getExpressionSuggestionsByType(
if (rightArg.args.some(isTimeIntervalItem)) {
const lastFnArg = rightArg.args[rightArg.args.length - 1];
const lastFnArgType = extractFinalTypeFromArg(lastFnArg, references);
if (isNumericType(lastFnArgType) && isLiteralItem(lastFnArg))
if (lastFnArgType === 'number' && isLiteralItem(lastFnArg))
// ... EVAL var = 1 year + 2 <suggest>
suggestions.push(...getCompatibleLiterals(command.name, ['time_literal_unit']));
}
@ -779,7 +777,7 @@ async function getExpressionSuggestionsByType(
if (nodeArg.args.some(isTimeIntervalItem)) {
const lastFnArg = nodeArg.args[nodeArg.args.length - 1];
const lastFnArgType = extractFinalTypeFromArg(lastFnArg, references);
if (isNumericType(lastFnArgType) && isLiteralItem(lastFnArg))
if (lastFnArgType === 'number' && isLiteralItem(lastFnArg))
// ... EVAL var = 1 year + 2 <suggest>
suggestions.push(...getCompatibleLiterals(command.name, ['time_literal_unit']));
}
@ -795,10 +793,7 @@ async function getExpressionSuggestionsByType(
suggestions.push(...buildConstantsDefinitions(argDef.values));
}
// If the type is specified try to dig deeper in the definition to suggest the best candidate
if (
['string', 'text', 'keyword', 'boolean', ...ESQL_NUMBER_TYPES].includes(argDef.type) &&
!argDef.values
) {
if (['string', 'number', 'boolean'].includes(argDef.type) && !argDef.values) {
// it can be just literal values (i.e. "string")
if (argDef.constantOnly) {
// ... | <COMMAND> ... <suggest>
@ -976,7 +971,6 @@ async function getBuiltinFunctionNextArgument(
) {
const suggestions = [];
const isFnComplete = isFunctionArgComplete(nodeArg, references);
if (isFnComplete.complete) {
// i.e. ... | <COMMAND> field > 0 <suggest>
// i.e. ... | <COMMAND> field + otherN <suggest>
@ -1007,16 +1001,17 @@ async function getBuiltinFunctionNextArgument(
suggestions.push(listCompleteItem);
} else {
const finalType = nestedType || nodeArgType || 'any';
const supportedTypes = getSupportedTypesForBinaryOperators(fnDef, finalType);
suggestions.push(
...(await getFieldsOrFunctionsSuggestions(
// this is a special case with AND/OR
// <COMMAND> expression AND/OR <suggest>
// technically another boolean value should be suggested, but it is a better experience
// to actually suggest a wider set of fields/functions
finalType === 'boolean' && getFunctionDefinition(nodeArg.name)?.type === 'builtin'
? ['any']
: supportedTypes,
[
finalType === 'boolean' && getFunctionDefinition(nodeArg.name)?.type === 'builtin'
? 'any'
: finalType,
],
command.name,
option?.name,
getFieldsByType,
@ -1326,7 +1321,7 @@ async function getFunctionArgsSuggestions(
// for eval and row commands try also to complete numeric literals with time intervals where possible
if (arg) {
if (command.name !== 'stats') {
if (isLiteralItem(arg) && isNumericType(arg.literalType)) {
if (isLiteralItem(arg) && arg.literalType === 'number') {
// ... | EVAL fn(2 <suggest>)
suggestions.push(
...(await getFieldsOrFunctionsSuggestions(

View file

@ -22,8 +22,7 @@ import {
import { shouldBeQuotedSource, getCommandDefinition, shouldBeQuotedText } from '../shared/helpers';
import { buildDocumentation, buildFunctionDocumentation } from './documentation_util';
import { DOUBLE_BACKTICK, SINGLE_TICK_REGEX } from '../shared/constants';
import { ESQLRealField } from '../validation/types';
import { isNumericType } from '../shared/esql_types';
import type { ESQLRealField } from '../validation/types';
const allFunctions = statsAggregationFunctionDefinitions
.concat(evalFunctionDefinitions)
@ -360,7 +359,7 @@ export function getUnitDuration(unit: number = 1) {
*/
export function getCompatibleLiterals(commandName: string, types: string[], names?: string[]) {
const suggestions: SuggestionRawDefinition[] = [];
if (types.some(isNumericType)) {
if (types.includes('number')) {
if (commandName === 'limit') {
// suggest 10/100/1000 for limit
suggestions.push(...buildConstantsDefinitions(['10', '100', '1000'], ''));

View file

@ -80,15 +80,3 @@ export function removeQuoteForSuggestedSources(suggestions: SuggestionRawDefinit
text: d.text.startsWith('"') && d.text.endsWith('"') ? d.text.slice(1, -1) : d.text,
}));
}
export function getSupportedTypesForBinaryOperators(
fnDef: FunctionDefinition | undefined,
previousType: string
) {
// Retrieve list of all 'right' supported types that match the left hand side of the function
return fnDef && Array.isArray(fnDef?.signatures)
? fnDef.signatures
.filter(({ params }) => params.find((p) => p.name === 'left' && p.type === previousType))
.map(({ params }) => params[1].type)
: [previousType];
}

View file

@ -7,18 +7,15 @@
*/
import { i18n } from '@kbn/i18n';
import { ESQL_COMMON_NUMERIC_TYPES, ESQL_NUMBER_TYPES } from '../shared/esql_types';
import type { FunctionDefinition, FunctionParameterType, FunctionReturnType } from './types';
import type { FunctionDefinition, FunctionParameterType } from './types';
function createNumericAggDefinition({
name,
description,
returnType,
args = [],
}: {
name: string;
description: string;
returnType?: (numericType: FunctionParameterType) => FunctionReturnType;
args?: Array<{
name: string;
type: FunctionParameterType;
@ -33,9 +30,9 @@ function createNumericAggDefinition({
description,
supportedCommands: ['stats', 'metrics'],
signatures: [
...ESQL_NUMBER_TYPES.map((numericType) => ({
{
params: [
{ name: 'column', type: numericType, noNestingFunctions: true },
{ name: 'column', type: 'number', noNestingFunctions: true },
...args.map(({ name: paramName, type, constantOnly }) => ({
name: paramName,
type,
@ -43,8 +40,8 @@ function createNumericAggDefinition({
constantOnly,
})),
],
returnType: returnType ? returnType(numericType) : numericType,
})),
returnType: 'number',
},
],
examples: [
`from index | stats result = ${name}(field${extraParamsExample})`,
@ -59,28 +56,18 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [
description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.avgDoc', {
defaultMessage: 'Returns the average of the values in a field',
}),
returnType: () => 'double' as FunctionReturnType,
},
{
name: 'sum',
description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.sumDoc', {
defaultMessage: 'Returns the sum of the values in a field.',
}),
returnType: (numericType: FunctionParameterType): FunctionReturnType => {
switch (numericType) {
case 'double':
return 'double';
default:
return 'long';
}
},
},
{
name: 'median',
description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.medianDoc', {
defaultMessage: 'Returns the 50% percentile.',
}),
returnType: () => 'double' as FunctionReturnType,
},
{
name: 'median_absolute_deviation',
@ -91,42 +78,20 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [
'Returns the median of each data points deviation from the median of the entire sample.',
}
),
returnType: () => 'double' as FunctionReturnType,
},
{
name: 'percentile',
description: i18n.translate(
'kbn-esql-validation-autocomplete.esql.definitions.percentiletDoc',
{
defaultMessage: 'Returns the n percentile of a field.',
}
),
args: [{ name: 'percentile', type: 'number' as const, value: '90', constantOnly: true }],
},
]
.map(createNumericAggDefinition)
.concat([
{
name: 'percentile',
description: i18n.translate(
'kbn-esql-validation-autocomplete.esql.definitions.percentiletDoc',
{
defaultMessage: 'Returns the n percentile of a field.',
}
),
type: 'agg',
supportedCommands: ['stats', 'metrics'],
signatures: [
...ESQL_COMMON_NUMERIC_TYPES.map((numericType: FunctionParameterType) => {
return ESQL_COMMON_NUMERIC_TYPES.map((weightType: FunctionParameterType) => ({
params: [
{
name: 'column',
type: numericType,
noNestingFunctions: true,
},
{
name: 'percentile',
type: weightType,
noNestingFunctions: true,
constantOnly: true,
},
],
returnType: 'double' as FunctionReturnType,
}));
}).flat(),
],
},
{
name: 'max',
description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.maxDoc', {
@ -135,17 +100,13 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [
type: 'agg',
supportedCommands: ['stats', 'metrics'],
signatures: [
...ESQL_COMMON_NUMERIC_TYPES.map((type) => ({
params: [{ name: 'column', type, noNestingFunctions: true }],
returnType: type,
})),
{
params: [{ name: 'column', type: 'date', noNestingFunctions: true }],
returnType: 'date',
params: [{ name: 'column', type: 'number', noNestingFunctions: true }],
returnType: 'number',
},
{
params: [{ name: 'column', type: 'date_period', noNestingFunctions: true }],
returnType: 'date_period',
params: [{ name: 'column', type: 'date', noNestingFunctions: true }],
returnType: 'number',
},
{
params: [{ name: 'column', type: 'boolean', noNestingFunctions: true }],
@ -166,17 +127,13 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [
type: 'agg',
supportedCommands: ['stats', 'metrics'],
signatures: [
...ESQL_COMMON_NUMERIC_TYPES.map((type) => ({
params: [{ name: 'column', type, noNestingFunctions: true }],
returnType: type,
})),
{
params: [{ name: 'column', type: 'date', noNestingFunctions: true }],
returnType: 'date',
params: [{ name: 'column', type: 'number', noNestingFunctions: true }],
returnType: 'number',
},
{
params: [{ name: 'column', type: 'date_period', noNestingFunctions: true }],
returnType: 'date_period',
params: [{ name: 'column', type: 'date', noNestingFunctions: true }],
returnType: 'number',
},
{
params: [{ name: 'column', type: 'boolean', noNestingFunctions: true }],
@ -209,7 +166,7 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [
optional: true,
},
],
returnType: 'long',
returnType: 'number',
},
],
examples: [`from index | stats result = count(field)`, `from index | stats count(field)`],
@ -228,14 +185,9 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [
{
params: [
{ name: 'column', type: 'any', noNestingFunctions: true },
...ESQL_NUMBER_TYPES.map((type) => ({
name: 'precision',
type,
noNestingFunctions: true,
optional: true,
})),
{ name: 'precision', type: 'number', noNestingFunctions: true, optional: true },
],
returnType: 'long',
returnType: 'number',
},
],
examples: [
@ -306,14 +258,14 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [
},
{
name: 'limit',
type: 'integer',
type: 'number',
noNestingFunctions: true,
optional: false,
constantOnly: true,
},
{
name: 'order',
type: 'keyword',
type: 'string',
noNestingFunctions: true,
optional: false,
constantOnly: true,
@ -340,25 +292,23 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [
),
supportedCommands: ['stats', 'metrics'],
signatures: [
...ESQL_COMMON_NUMERIC_TYPES.map((numericType: FunctionParameterType) => {
return ESQL_COMMON_NUMERIC_TYPES.map((weightType: FunctionParameterType) => ({
params: [
{
name: 'number',
type: numericType,
noNestingFunctions: true,
optional: false,
},
{
name: 'weight',
type: weightType,
noNestingFunctions: true,
optional: false,
},
],
returnType: 'double' as FunctionReturnType,
}));
}).flat(),
{
params: [
{
name: 'number',
type: 'number',
noNestingFunctions: true,
optional: false,
},
{
name: 'weight',
type: 'number',
noNestingFunctions: true,
optional: false,
},
],
returnType: 'number',
},
],
examples: [
`from employees | stats w_avg = weighted_avg(salary, height) by languages | eval w_avg = round(w_avg)`,

View file

@ -7,14 +7,14 @@
*/
import { i18n } from '@kbn/i18n';
import { ESQL_NUMBER_TYPES, isNumericType } from '../shared/esql_types';
import type { FunctionDefinition, FunctionParameterType, FunctionReturnType } from './types';
type MathFunctionSignature = [FunctionParameterType, FunctionParameterType, FunctionReturnType];
function createMathDefinition(
name: string,
functionSignatures: MathFunctionSignature[],
types: Array<
| (FunctionParameterType & FunctionReturnType)
| [FunctionParameterType, FunctionParameterType, FunctionReturnType]
>,
description: string,
validate?: FunctionDefinition['validate']
): FunctionDefinition {
@ -24,41 +24,28 @@ function createMathDefinition(
description,
supportedCommands: ['eval', 'where', 'row', 'stats', 'metrics', 'sort'],
supportedOptions: ['by'],
signatures: functionSignatures.map((functionSignature) => {
const [lhs, rhs, result] = functionSignature;
signatures: types.map((type) => {
if (Array.isArray(type)) {
return {
params: [
{ name: 'left', type: type[0] },
{ name: 'right', type: type[1] },
],
returnType: type[2],
};
}
return {
params: [
{ name: 'left', type: lhs },
{ name: 'right', type: rhs },
{ name: 'left', type },
{ name: 'right', type },
],
returnType: result,
returnType: type,
};
}),
validate,
};
}
// https://www.elastic.co/guide/en/elasticsearch/reference/master/esql-functions-operators.html#_less_than
const baseComparisonTypeTable: MathFunctionSignature[] = [
['date', 'date', 'boolean'],
['double', 'double', 'boolean'],
['double', 'integer', 'boolean'],
['double', 'long', 'boolean'],
['integer', 'double', 'boolean'],
['integer', 'integer', 'boolean'],
['integer', 'long', 'boolean'],
['ip', 'ip', 'boolean'],
['keyword', 'keyword', 'boolean'],
['keyword', 'text', 'boolean'],
['long', 'double', 'boolean'],
['long', 'integer', 'boolean'],
['long', 'long', 'boolean'],
['text', 'keyword', 'boolean'],
['text', 'text', 'boolean'],
['unsigned_long', 'unsigned_long', 'boolean'],
['version', 'version', 'boolean'],
];
function createComparisonDefinition(
{
name,
@ -71,17 +58,6 @@ function createComparisonDefinition(
},
validate?: FunctionDefinition['validate']
): FunctionDefinition {
const commonSignatures = baseComparisonTypeTable.map((functionSignature) => {
const [lhs, rhs, result] = functionSignature;
return {
params: [
{ name: 'left', type: lhs },
{ name: 'right', type: rhs },
],
returnType: result,
};
});
return {
type: 'builtin' as const,
name,
@ -90,7 +66,41 @@ function createComparisonDefinition(
supportedOptions: ['by'],
validate,
signatures: [
...commonSignatures,
{
params: [
{ name: 'left', type: 'number' },
{ name: 'right', type: 'number' },
],
returnType: 'boolean',
},
{
params: [
{ name: 'left', type: 'string' },
{ name: 'right', type: 'string' },
],
returnType: 'boolean',
},
{
params: [
{ name: 'left', type: 'date' },
{ name: 'right', type: 'date' },
],
returnType: 'boolean',
},
{
params: [
{ name: 'left', type: 'ip' },
{ name: 'right', type: 'ip' },
],
returnType: 'boolean',
},
{
params: [
{ name: 'left', type: 'version' },
{ name: 'right', type: 'version' },
],
returnType: 'boolean',
},
// constant strings okay because of implicit casting for
// string to version and ip
//
@ -103,13 +113,13 @@ function createComparisonDefinition(
{
params: [
{ name: 'left', type },
{ name: 'right', type: 'text' as const, constantOnly: true },
{ name: 'right', type: 'string' as const, constantOnly: true },
],
returnType: 'boolean' as const,
},
{
params: [
{ name: 'left', type: 'text' as const, constantOnly: true },
{ name: 'right', type: 'string' as const, constantOnly: true },
{ name: 'right', type },
],
returnType: 'boolean' as const,
@ -120,111 +130,31 @@ function createComparisonDefinition(
};
}
const addTypeTable: MathFunctionSignature[] = [
['date_period', 'date_period', 'date_period'],
['date_period', 'date', 'date'],
['date', 'date_period', 'date'],
['date', 'time_duration', 'date'],
['date', 'time_literal', 'date'],
['double', 'double', 'double'],
['double', 'integer', 'double'],
['double', 'long', 'double'],
['integer', 'double', 'double'],
['integer', 'integer', 'integer'],
['integer', 'long', 'long'],
['long', 'double', 'double'],
['long', 'integer', 'long'],
['long', 'long', 'long'],
['time_duration', 'date', 'date'],
['time_duration', 'time_duration', 'time_duration'],
['unsigned_long', 'unsigned_long', 'unsigned_long'],
['time_literal', 'date', 'date'],
];
const subtractTypeTable: MathFunctionSignature[] = [
['date_period', 'date_period', 'date_period'],
['date', 'date_period', 'date'],
['date', 'time_duration', 'date'],
['date', 'time_literal', 'date'],
['double', 'double', 'double'],
['double', 'integer', 'double'],
['double', 'long', 'double'],
['integer', 'double', 'double'],
['integer', 'integer', 'integer'],
['integer', 'long', 'long'],
['long', 'double', 'double'],
['long', 'integer', 'long'],
['long', 'long', 'long'],
['time_duration', 'date', 'date'],
['time_duration', 'time_duration', 'time_duration'],
['unsigned_long', 'unsigned_long', 'unsigned_long'],
['time_literal', 'date', 'date'],
];
const multiplyTypeTable: MathFunctionSignature[] = [
['double', 'double', 'double'],
['double', 'integer', 'double'],
['double', 'long', 'double'],
['integer', 'double', 'double'],
['integer', 'integer', 'integer'],
['integer', 'long', 'long'],
['long', 'double', 'double'],
['long', 'integer', 'long'],
['long', 'long', 'long'],
['unsigned_long', 'unsigned_long', 'unsigned_long'],
];
const divideTypeTable: MathFunctionSignature[] = [
['double', 'double', 'double'],
['double', 'integer', 'double'],
['double', 'long', 'double'],
['integer', 'double', 'double'],
['integer', 'integer', 'integer'],
['integer', 'long', 'long'],
['long', 'double', 'double'],
['long', 'integer', 'long'],
['long', 'long', 'long'],
['unsigned_long', 'unsigned_long', 'unsigned_long'],
];
const modulusTypeTable: MathFunctionSignature[] = [
['double', 'double', 'double'],
['double', 'integer', 'double'],
['double', 'long', 'double'],
['integer', 'double', 'double'],
['integer', 'integer', 'integer'],
['integer', 'long', 'long'],
['long', 'double', 'double'],
['long', 'integer', 'long'],
['long', 'long', 'long'],
['unsigned_long', 'unsigned_long', 'unsigned_long'],
];
export const mathFunctions: FunctionDefinition[] = [
createMathDefinition(
'+',
addTypeTable,
['number', ['date', 'time_literal', 'date'], ['time_literal', 'date', 'date']],
i18n.translate('kbn-esql-validation-autocomplete.esql.definition.addDoc', {
defaultMessage: 'Add (+)',
})
),
createMathDefinition(
'-',
subtractTypeTable,
['number', ['date', 'time_literal', 'date'], ['time_literal', 'date', 'date']],
i18n.translate('kbn-esql-validation-autocomplete.esql.definition.subtractDoc', {
defaultMessage: 'Subtract (-)',
})
),
createMathDefinition(
'*',
multiplyTypeTable,
['number'],
i18n.translate('kbn-esql-validation-autocomplete.esql.definition.multiplyDoc', {
defaultMessage: 'Multiply (*)',
})
),
createMathDefinition(
'/',
divideTypeTable,
['number'],
i18n.translate('kbn-esql-validation-autocomplete.esql.definition.divideDoc', {
defaultMessage: 'Divide (/)',
}),
@ -232,7 +162,7 @@ export const mathFunctions: FunctionDefinition[] = [
const [left, right] = fnDef.args;
const messages = [];
if (!Array.isArray(left) && !Array.isArray(right)) {
if (right.type === 'literal' && isNumericType(right.literalType)) {
if (right.type === 'literal' && right.literalType === 'number') {
if (right.value === 0) {
messages.push({
type: 'warning' as const,
@ -257,7 +187,7 @@ export const mathFunctions: FunctionDefinition[] = [
),
createMathDefinition(
'%',
modulusTypeTable,
['number'],
i18n.translate('kbn-esql-validation-autocomplete.esql.definition.moduleDoc', {
defaultMessage: 'Module (%)',
}),
@ -265,7 +195,7 @@ export const mathFunctions: FunctionDefinition[] = [
const [left, right] = fnDef.args;
const messages = [];
if (!Array.isArray(left) && !Array.isArray(right)) {
if (right.type === 'literal' && isNumericType(right.literalType)) {
if (right.type === 'literal' && right.literalType === 'number') {
if (right.value === 0) {
messages.push({
type: 'warning' as const,
@ -314,7 +244,7 @@ const comparisonFunctions: FunctionDefinition[] = [
},
{
params: [
{ name: 'left', type: 'string' as const, constantOnly: true },
{ name: 'right', type: 'string' as const, constantOnly: true },
{ name: 'right', type: 'boolean' as const },
],
returnType: 'boolean' as const,
@ -344,7 +274,7 @@ const comparisonFunctions: FunctionDefinition[] = [
},
{
params: [
{ name: 'left', type: 'string' as const, constantOnly: true },
{ name: 'right', type: 'string' as const, constantOnly: true },
{ name: 'right', type: 'boolean' as const },
],
returnType: 'boolean' as const,
@ -417,15 +347,8 @@ const likeFunctions: FunctionDefinition[] = [
signatures: [
{
params: [
{ name: 'left', type: 'text' as const },
{ name: 'right', type: 'text' as const },
],
returnType: 'boolean',
},
{
params: [
{ name: 'left', type: 'keyword' as const },
{ name: 'right', type: 'keyword' as const },
{ name: 'left', type: 'string' as const },
{ name: 'right', type: 'string' as const },
],
returnType: 'boolean',
},
@ -460,24 +383,17 @@ const inFunctions: FunctionDefinition[] = [
description,
supportedCommands: ['eval', 'where', 'row', 'sort'],
signatures: [
...ESQL_NUMBER_TYPES.map((type) => ({
params: [
{ name: 'left', type: type as FunctionParameterType },
{ name: 'right', type: 'any[]' as FunctionParameterType },
],
returnType: 'boolean' as FunctionReturnType,
})),
{
params: [
{ name: 'left', type: 'keyword' },
{ name: 'left', type: 'number' },
{ name: 'right', type: 'any[]' },
],
returnType: 'boolean',
},
{
params: [
{ name: 'left', type: 'text' },
{ name: 'left', type: 'string' },
{ name: 'right', type: 'any[]' },
],
returnType: 'boolean',

View file

@ -273,7 +273,7 @@ export const commandDefinitions: CommandDefinition[] = [
examples: ['… | limit 100', '… | limit 0'],
signature: {
multipleParams: false,
params: [{ name: 'size', type: 'integer', constantOnly: true }],
params: [{ name: 'size', type: 'number', constantOnly: true }],
},
options: [],
modes: [],
@ -390,7 +390,6 @@ export const commandDefinitions: CommandDefinition[] = [
signature: {
multipleParams: false,
params: [
// innerType: 'string' is interpreted as keyword and text (see columnParamsWithInnerTypes)
{ name: 'column', type: 'column', innerType: 'string' },
{ name: 'pattern', type: 'string', constantOnly: true },
],
@ -408,7 +407,6 @@ export const commandDefinitions: CommandDefinition[] = [
signature: {
multipleParams: false,
params: [
// innerType: 'string' is interpreted as keyword and text (see columnParamsWithInnerTypes)
{ name: 'column', type: 'column', innerType: 'string' },
{ name: 'pattern', type: 'string', constantOnly: true },
],

View file

@ -7,53 +7,8 @@
*/
import { i18n } from '@kbn/i18n';
import { FunctionDefinition, FunctionParameterType, FunctionReturnType } from './types';
import { FunctionDefinition } from './types';
const groupingTypeTable: Array<
[
FunctionParameterType,
FunctionParameterType,
FunctionParameterType | null,
FunctionParameterType | null,
FunctionReturnType
]
> = [
// field // bucket //from // to //result
['date', 'date_period', null, null, 'date'],
['date', 'integer', 'date', 'date', 'date'],
// Modified time_duration to time_literal
['date', 'time_literal', null, null, 'date'],
['double', 'double', null, null, 'double'],
['double', 'integer', 'double', 'double', 'double'],
['double', 'integer', 'double', 'integer', 'double'],
['double', 'integer', 'double', 'long', 'double'],
['double', 'integer', 'integer', 'double', 'double'],
['double', 'integer', 'integer', 'integer', 'double'],
['double', 'integer', 'integer', 'long', 'double'],
['double', 'integer', 'long', 'double', 'double'],
['double', 'integer', 'long', 'integer', 'double'],
['double', 'integer', 'long', 'long', 'double'],
['integer', 'double', null, null, 'double'],
['integer', 'integer', 'double', 'double', 'double'],
['integer', 'integer', 'double', 'integer', 'double'],
['integer', 'integer', 'double', 'long', 'double'],
['integer', 'integer', 'integer', 'double', 'double'],
['integer', 'integer', 'integer', 'integer', 'double'],
['integer', 'integer', 'integer', 'long', 'double'],
['integer', 'integer', 'long', 'double', 'double'],
['integer', 'integer', 'long', 'integer', 'double'],
['integer', 'integer', 'long', 'long', 'double'],
['long', 'double', null, null, 'double'],
['long', 'integer', 'double', 'double', 'double'],
['long', 'integer', 'double', 'integer', 'double'],
['long', 'integer', 'double', 'long', 'double'],
['long', 'integer', 'integer', 'double', 'double'],
['long', 'integer', 'integer', 'integer', 'double'],
['long', 'integer', 'integer', 'long', 'double'],
['long', 'integer', 'long', 'double', 'double'],
['long', 'integer', 'long', 'integer', 'double'],
['long', 'integer', 'long', 'long', 'double'],
];
export const groupingFunctionDefinitions: FunctionDefinition[] = [
{
name: 'bucket',
@ -66,18 +21,65 @@ export const groupingFunctionDefinitions: FunctionDefinition[] = [
supportedCommands: ['stats'],
supportedOptions: ['by'],
signatures: [
...groupingTypeTable.map((signature) => {
const [fieldType, bucketType, fromType, toType, resultType] = signature;
return {
params: [
{ name: 'field', type: fieldType },
{ name: 'buckets', type: bucketType, constantOnly: true },
...(fromType ? [{ name: 'startDate', type: fromType, constantOnly: true }] : []),
...(toType ? [{ name: 'endDate', type: toType, constantOnly: true }] : []),
],
returnType: resultType,
};
}),
{
params: [
{ name: 'field', type: 'date' },
{ name: 'buckets', type: 'time_literal', constantOnly: true },
],
returnType: 'date',
},
{
params: [
{ name: 'field', type: 'number' },
{ name: 'buckets', type: 'number', constantOnly: true },
],
returnType: 'number',
},
{
params: [
{ name: 'field', type: 'date' },
{ name: 'buckets', type: 'number', constantOnly: true },
{ name: 'startDate', type: 'string', constantOnly: true },
{ name: 'endDate', type: 'string', constantOnly: true },
],
returnType: 'date',
},
{
params: [
{ name: 'field', type: 'date' },
{ name: 'buckets', type: 'number', constantOnly: true },
{ name: 'startDate', type: 'date', constantOnly: true },
{ name: 'endDate', type: 'date', constantOnly: true },
],
returnType: 'date',
},
{
params: [
{ name: 'field', type: 'date' },
{ name: 'buckets', type: 'number', constantOnly: true },
{ name: 'startDate', type: 'string', constantOnly: true },
{ name: 'endDate', type: 'date', constantOnly: true },
],
returnType: 'date',
},
{
params: [
{ name: 'field', type: 'date' },
{ name: 'buckets', type: 'number', constantOnly: true },
{ name: 'startDate', type: 'date', constantOnly: true },
{ name: 'endDate', type: 'string', constantOnly: true },
],
returnType: 'date',
},
{
params: [
{ name: 'field', type: 'number' },
{ name: 'buckets', type: 'number', constantOnly: true },
{ name: 'startValue', type: 'number', constantOnly: true },
{ name: 'endValue', type: 'number', constantOnly: true },
],
returnType: 'number',
},
],
examples: [
'from index | eval hd = bucket(bytes, 1 hour)',

View file

@ -8,20 +8,10 @@
import type { ESQLCommand, ESQLCommandOption, ESQLFunction, ESQLMessage } from '@kbn/esql-ast';
// Currently, partial of the full list
// https://github.com/elastic/elasticsearch/blob/main/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/DataType.java
export const supportedFieldTypes = [
'double',
'unsigned_long',
'long',
'integer',
'counter_integer',
'counter_long',
'counter_double',
'number',
'date',
'date_period',
'text',
'keyword',
'string',
'boolean',
'ip',
'cartesian_point',
@ -38,43 +28,21 @@ export type SupportedFieldType = (typeof supportedFieldTypes)[number];
export type FunctionParameterType =
| SupportedFieldType
| 'string'
| 'null'
| 'any'
| 'chrono_literal'
| 'time_literal'
| 'time_duration'
| 'double[]'
| 'unsigned_long[]'
| 'long[]'
| 'integer[]'
| 'counter_integer[]'
| 'counter_long[]'
| 'counter_double[]'
| 'number[]'
| 'string[]'
| 'keyword[]'
| 'text[]'
| 'boolean[]'
| 'any[]'
| 'datetime[]'
| 'date_period[]';
| 'date[]';
export type FunctionReturnType =
| 'double'
| 'unsigned_long'
| 'long'
| 'integer'
| 'int'
| 'counter_integer'
| 'counter_long'
| 'counter_double'
| 'number'
| 'date'
| 'date_period'
| 'time_duration'
| 'any'
| 'boolean'
| 'text'
| 'keyword'
| 'string'
| 'cartesian_point'
| 'cartesian_shape'

View file

@ -0,0 +1,44 @@
/*
* 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.
*/
const ESQL_NUMBER_TYPES = [
'double',
'unsigned_long',
'long',
'integer',
'int',
'counter_integer',
'counter_long',
'counter_double',
];
const ESQL_TEXT_TYPES = ['text', 'keyword', 'string'];
export const esqlToKibanaType = (elasticsearchType: string) => {
if (ESQL_NUMBER_TYPES.includes(elasticsearchType)) {
return 'number';
}
if (ESQL_TEXT_TYPES.includes(elasticsearchType)) {
return 'string';
}
if (['datetime', 'time_duration'].includes(elasticsearchType)) {
return 'date';
}
if (elasticsearchType === 'bool') {
return 'boolean';
}
if (elasticsearchType === 'date_period') {
return 'time_literal'; // TODO - consider aligning with Elasticsearch
}
return elasticsearchType;
};

View file

@ -1,49 +0,0 @@
/*
* 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 { ESQLDecimalLiteral, ESQLNumericLiteralType } from '@kbn/esql-ast/src/types';
export const ESQL_COMMON_NUMERIC_TYPES = ['double', 'long', 'integer'] as const;
export const ESQL_NUMERIC_DECIMAL_TYPES = [
'double',
'unsigned_long',
'long',
'counter_long',
'counter_double',
] as const;
export const ESQL_NUMBER_TYPES = [
'integer',
'counter_integer',
...ESQL_NUMERIC_DECIMAL_TYPES,
] as const;
export const ESQL_STRING_TYPES = ['keyword', 'text'] as const;
export const ESQL_DATE_TYPES = ['datetime', 'date_period'] as const;
/**
*
* @param type
* @returns
*/
export function isStringType(type: unknown) {
return typeof type === 'string' && ['keyword', 'text'].includes(type);
}
export function isNumericType(type: unknown): type is ESQLNumericLiteralType {
return (
typeof type === 'string' &&
[...ESQL_NUMBER_TYPES, 'decimal'].includes(type as (typeof ESQL_NUMBER_TYPES)[number])
);
}
export function isNumericDecimalType(type: unknown): type is ESQLDecimalLiteral {
return (
typeof type === 'string' &&
ESQL_NUMERIC_DECIMAL_TYPES.includes(type as (typeof ESQL_NUMERIC_DECIMAL_TYPES)[number])
);
}

View file

@ -33,7 +33,7 @@ import {
withOption,
appendSeparatorOption,
} from '../definitions/options';
import {
import type {
CommandDefinition,
CommandOptionsDefinition,
FunctionParameter,
@ -43,7 +43,7 @@ import {
} from '../definitions/types';
import type { ESQLRealField, ESQLVariable, ReferenceMaps } from '../validation/types';
import { removeMarkerArgFromArgsList } from './context';
import { isNumericDecimalType } from './esql_types';
import { esqlToKibanaType } from './esql_to_kibana_type';
import type { ReasonTypes } from './types';
export function nonNullable<T>(v: T): v is NonNullable<T> {
@ -226,14 +226,6 @@ function compareLiteralType(argType: string, item: ESQLLiteral) {
return true;
}
if (item.literalType === 'decimal' && isNumericDecimalType(argType)) {
return true;
}
if (item.literalType === 'string' && (argType === 'text' || argType === 'keyword')) {
return true;
}
if (item.literalType !== 'string') {
if (argType === item.literalType) {
return true;
@ -242,7 +234,7 @@ function compareLiteralType(argType: string, item: ESQLLiteral) {
}
// date-type parameters accept string literals because of ES auto-casting
return ['string', 'date', 'date', 'date_period'].includes(argType);
return ['string', 'date'].includes(argType);
}
/**
@ -253,14 +245,7 @@ export function lookupColumn(
{ fields, variables }: Pick<ReferenceMaps, 'fields' | 'variables'>
): ESQLRealField | ESQLVariable | undefined {
const columnName = getQuotedColumnName(column);
return (
fields.get(columnName) ||
variables.get(columnName)?.[0] ||
// It's possible columnName has backticks "`fieldName`"
// so we need to access the original name as well
fields.get(column.name) ||
variables.get(column.name)?.[0]
);
return fields.get(columnName) || variables.get(columnName)?.[0];
}
const ARRAY_REGEXP = /\[\]$/;
@ -270,19 +255,10 @@ export function isArrayType(type: string) {
}
const arrayToSingularMap: Map<FunctionParameterType, FunctionParameterType> = new Map([
['double[]', 'double'],
['unsigned_long[]', 'unsigned_long'],
['long[]', 'long'],
['integer[]', 'integer'],
['counter_integer[]', 'counter_integer'],
['counter_long[]', 'counter_long'],
['counter_double[]', 'counter_double'],
['string[]', 'string'],
['keyword[]', 'keyword'],
['text[]', 'text'],
['datetime[]', 'date'],
['date_period[]', 'date_period'],
['number[]', 'number'],
['date[]', 'date'],
['boolean[]', 'boolean'],
['string[]', 'string'],
['any[]', 'any'],
]);
@ -431,8 +407,7 @@ export function checkFunctionArgMatchesDefinition(
return true;
}
if (arg.type === 'literal') {
const matched = compareLiteralType(argType, arg);
return matched;
return compareLiteralType(argType, arg);
}
if (arg.type === 'function') {
if (isSupportedFunction(arg.name, parentCommand).supported) {
@ -453,21 +428,11 @@ export function checkFunctionArgMatchesDefinition(
}
const wrappedTypes = Array.isArray(validHit.type) ? validHit.type : [validHit.type];
// if final type is of type any make it pass for now
return wrappedTypes.some(
(ct) =>
['any', 'null'].includes(ct) ||
argType === ct ||
(ct === 'string' && ['text', 'keyword'].includes(argType))
);
return wrappedTypes.some((ct) => ['any', 'null'].includes(ct) || argType === ct);
}
if (arg.type === 'inlineCast') {
const lowerArgType = argType?.toLowerCase();
const lowerArgCastType = arg.castType?.toLowerCase();
return (
lowerArgType === lowerArgCastType ||
// for valid shorthand casts like 321.12::int or "false"::bool
(['int', 'bool'].includes(lowerArgCastType) && argType.startsWith(lowerArgCastType))
);
// TODO - remove with https://github.com/elastic/kibana/issues/174710
return argType === esqlToKibanaType(arg.castType);
}
}

View file

@ -35,7 +35,7 @@ function addToVariables(
if (isColumnItem(oldArg) && isColumnItem(newArg)) {
const newVariable: ESQLVariable = {
name: newArg.name,
type: 'double' /* fallback to number */,
type: 'number' /* fallback to number */,
location: newArg.location,
};
// Now workout the exact type
@ -107,7 +107,7 @@ function addVariableFromAssignment(
const rightHandSideArgType = getAssignRightHandSideType(assignOperation.args[1], fields);
addToVariableOccurrencies(variables, {
name: assignOperation.args[0].name,
type: rightHandSideArgType || 'double' /* fallback to number */,
type: rightHandSideArgType || 'number' /* fallback to number */,
location: assignOperation.args[0].location,
});
}
@ -125,7 +125,7 @@ function addVariableFromExpression(
queryString,
expressionOperation.location
);
const expressionType = 'double';
const expressionType = 'number';
addToVariableOccurrencies(variables, {
name: forwardThinkingVariableName,
type: expressionType,

View file

@ -31,7 +31,7 @@ export const setup = async () => {
return await validateQuery(query, getAstAndSyntaxErrors, opts, cb);
};
const assertErrors = (errors: unknown[], expectedErrors: string[], query?: string) => {
const assertErrors = (errors: unknown[], expectedErrors: string[]) => {
const errorMessages: string[] = [];
for (const error of errors) {
if (error && typeof error === 'object') {
@ -46,16 +46,7 @@ export const setup = async () => {
errorMessages.push(String(error));
}
}
try {
expect(errorMessages.sort()).toStrictEqual(expectedErrors.sort());
} catch (error) {
throw Error(`${query}\n
Received:
'${errorMessages.sort()}'
Expected:
${expectedErrors.sort()}`);
}
expect(errorMessages.sort()).toStrictEqual(expectedErrors.sort());
};
const expectErrors = async (
@ -66,9 +57,9 @@ export const setup = async () => {
cb: ESQLCallbacks = callbacks
) => {
const { errors, warnings } = await validateQuery(query, getAstAndSyntaxErrors, opts, cb);
assertErrors(errors, expectedErrors, query);
assertErrors(errors, expectedErrors);
if (expectedWarnings) {
assertErrors(warnings, expectedWarnings, query);
assertErrors(warnings, expectedWarnings);
}
};

View file

@ -85,7 +85,7 @@ export const validationMetricsCommandTestSuite = (setup: helpers.Setup) => {
await expectErrors(`METRICS average()`, ['Unknown index [average()]']);
await expectErrors(`metrics custom_function()`, ['Unknown index [custom_function()]']);
await expectErrors(`metrics indexes*`, ['Unknown index [indexes*]']);
await expectErrors('metrics doubleField', ['Unknown index [doubleField]']);
await expectErrors('metrics numberField', ['Unknown index [numberField]']);
await expectErrors('metrics policy', ['Unknown index [policy]']);
});
});
@ -95,26 +95,26 @@ export const validationMetricsCommandTestSuite = (setup: helpers.Setup) => {
const { expectErrors } = await setup();
await expectErrors('METRICS a_index count()', []);
await expectErrors('metrics a_index avg(doubleField) by 1', []);
await expectErrors('metrics a_index count(`doubleField`)', []);
await expectErrors('metrics a_index avg(numberField) by 1', []);
await expectErrors('metrics a_index count(`numberField`)', []);
await expectErrors('metrics a_index count(*)', []);
await expectErrors('metrics index var0 = count(*)', []);
await expectErrors('metrics a_index var0 = count()', []);
await expectErrors('metrics a_index var0 = avg(doubleField), count(*)', []);
await expectErrors('metrics a_index var0 = avg(numberField), count(*)', []);
await expectErrors(`metrics a_index sum(case(false, 0, 1))`, []);
await expectErrors(`metrics a_index var0 = sum( case(false, 0, 1))`, []);
await expectErrors('metrics a_index count(textField == "a" or null)', []);
await expectErrors('metrics other_index max(doubleField) by textField', []);
await expectErrors('metrics a_index count(stringField == "a" or null)', []);
await expectErrors('metrics other_index max(numberField) by stringField', []);
});
test('syntax errors', async () => {
const { expectErrors } = await setup();
await expectErrors('metrics a_index doubleField=', [
await expectErrors('metrics a_index numberField=', [
expect.any(String),
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
]);
await expectErrors('metrics a_index doubleField=5 by ', [
await expectErrors('metrics a_index numberField=5 by ', [
expect.any(String),
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
]);
@ -131,29 +131,29 @@ export const validationMetricsCommandTestSuite = (setup: helpers.Setup) => {
test('errors when no aggregation function specified', async () => {
const { expectErrors } = await setup();
await expectErrors('metrics a_index doubleField + 1', [
'At least one aggregation function required in [METRICS], found [doubleField+1]',
await expectErrors('metrics a_index numberField + 1', [
'At least one aggregation function required in [METRICS], found [numberField+1]',
]);
await expectErrors('metrics a_index a = doubleField + 1', [
'At least one aggregation function required in [METRICS], found [a=doubleField+1]',
await expectErrors('metrics a_index a = numberField + 1', [
'At least one aggregation function required in [METRICS], found [a=numberField+1]',
]);
await expectErrors('metrics a_index a = doubleField + 1, textField', [
'At least one aggregation function required in [METRICS], found [a=doubleField+1]',
'Expected an aggregate function or group but got [textField] of type [FieldAttribute]',
await expectErrors('metrics a_index a = numberField + 1, stringField', [
'At least one aggregation function required in [METRICS], found [a=numberField+1]',
'Expected an aggregate function or group but got [stringField] of type [FieldAttribute]',
]);
await expectErrors('metrics a_index doubleField + 1 by ipField', [
'At least one aggregation function required in [METRICS], found [doubleField+1]',
await expectErrors('metrics a_index numberField + 1 by ipField', [
'At least one aggregation function required in [METRICS], found [numberField+1]',
]);
});
test('errors on agg and non-agg mix', async () => {
const { expectErrors } = await setup();
await expectErrors('METRICS a_index sum( doubleField ) + abs( doubleField ) ', [
'Cannot combine aggregation and non-aggregation values in [METRICS], found [sum(doubleField)+abs(doubleField)]',
await expectErrors('METRICS a_index sum( numberField ) + abs( numberField ) ', [
'Cannot combine aggregation and non-aggregation values in [METRICS], found [sum(numberField)+abs(numberField)]',
]);
await expectErrors('METRICS a_index abs( doubleField + sum( doubleField )) ', [
'Cannot combine aggregation and non-aggregation values in [METRICS], found [abs(doubleField+sum(doubleField))]',
await expectErrors('METRICS a_index abs( numberField + sum( numberField )) ', [
'Cannot combine aggregation and non-aggregation values in [METRICS], found [abs(numberField+sum(numberField))]',
]);
});
@ -169,8 +169,8 @@ export const validationMetricsCommandTestSuite = (setup: helpers.Setup) => {
test('errors when input is not an aggregate function', async () => {
const { expectErrors } = await setup();
await expectErrors('metrics a_index doubleField ', [
'Expected an aggregate function or group but got [doubleField] of type [FieldAttribute]',
await expectErrors('metrics a_index numberField ', [
'Expected an aggregate function or group but got [numberField] of type [FieldAttribute]',
]);
});
@ -179,9 +179,9 @@ export const validationMetricsCommandTestSuite = (setup: helpers.Setup) => {
for (const subCommand of ['keep', 'drop', 'eval']) {
await expectErrors(
'metrics a_index count(`doubleField`) | ' +
'metrics a_index count(`numberField`) | ' +
subCommand +
' `count(``doubleField``)` ',
' `count(``numberField``)` ',
[]
);
}
@ -194,7 +194,7 @@ export const validationMetricsCommandTestSuite = (setup: helpers.Setup) => {
'Using wildcards (*) in round is not allowed',
]);
await expectErrors('metrics a_index count(count(*))', [
`Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [count(*)] of type [long]`,
`Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [count(*)] of type [number]`,
]);
});
});
@ -204,21 +204,21 @@ export const validationMetricsCommandTestSuite = (setup: helpers.Setup) => {
const { expectErrors } = await setup();
await expectErrors(
'metrics a_index avg(doubleField), percentile(doubleField, 50) by ipField',
'metrics a_index avg(numberField), percentile(numberField, 50) by ipField',
[]
);
await expectErrors(
'metrics a_index avg(doubleField), percentile(doubleField, 50) BY ipField',
'metrics a_index avg(numberField), percentile(numberField, 50) BY ipField',
[]
);
await expectErrors(
'metrics a_index avg(doubleField), percentile(doubleField, 50) + 1 by ipField',
'metrics a_index avg(numberField), percentile(numberField, 50) + 1 by ipField',
[]
);
await expectErrors('metrics a_index avg(doubleField) by textField | limit 100', []);
await expectErrors('metrics a_index avg(numberField) by stringField | limit 100', []);
for (const op of ['+', '-', '*', '/', '%']) {
await expectErrors(
`metrics a_index avg(doubleField) ${op} percentile(doubleField, 50) BY ipField`,
`metrics a_index avg(numberField) ${op} percentile(numberField, 50) BY ipField`,
[]
);
}
@ -227,9 +227,9 @@ export const validationMetricsCommandTestSuite = (setup: helpers.Setup) => {
test('syntax does not allow <grouping> clause without <aggregates>', async () => {
const { expectErrors } = await setup();
await expectErrors('metrics a_index BY textField', [
await expectErrors('metrics a_index BY stringField', [
'Expected an aggregate function or group but got [BY] of type [FieldAttribute]',
"SyntaxError: extraneous input 'textField' expecting <EOF>",
"SyntaxError: extraneous input 'stringField' expecting <EOF>",
]);
});
@ -239,7 +239,7 @@ export const validationMetricsCommandTestSuite = (setup: helpers.Setup) => {
await expectErrors('metrics a_index count(* + 1) BY ipField', [
"SyntaxError: no viable alternative at input 'count(* +'",
]);
await expectErrors('metrics a_index \n count(* + round(doubleField)) BY ipField', [
await expectErrors('metrics a_index \n count(* + round(numberField)) BY ipField', [
"SyntaxError: no viable alternative at input 'count(* +'",
]);
});
@ -251,20 +251,20 @@ export const validationMetricsCommandTestSuite = (setup: helpers.Setup) => {
'Using wildcards (*) in round is not allowed',
]);
await expectErrors('metrics a_index count(count(*)) BY ipField', [
`Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [count(*)] of type [long]`,
`Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [count(*)] of type [number]`,
]);
});
test('errors on unknown field', async () => {
const { expectErrors } = await setup();
await expectErrors('metrics a_index avg(doubleField) by wrongField', [
await expectErrors('metrics a_index avg(numberField) by wrongField', [
'Unknown column [wrongField]',
]);
await expectErrors('metrics a_index avg(doubleField) by wrongField + 1', [
await expectErrors('metrics a_index avg(numberField) by wrongField + 1', [
'Unknown column [wrongField]',
]);
await expectErrors('metrics a_index avg(doubleField) by var0 = wrongField + 1', [
await expectErrors('metrics a_index avg(numberField) by var0 = wrongField + 1', [
'Unknown column [wrongField]',
]);
});
@ -272,11 +272,11 @@ export const validationMetricsCommandTestSuite = (setup: helpers.Setup) => {
test('various errors', async () => {
const { expectErrors } = await setup();
await expectErrors('METRICS a_index avg(doubleField) by percentile(doubleField)', [
await expectErrors('METRICS a_index avg(numberField) by percentile(numberField)', [
'METRICS BY does not support function percentile',
]);
await expectErrors(
'METRICS a_index avg(doubleField) by textField, percentile(doubleField) by ipField',
'METRICS a_index avg(numberField) by stringField, percentile(numberField) by ipField',
[
"SyntaxError: mismatched input 'by' expecting <EOF>",
'METRICS BY does not support function percentile',

View file

@ -15,15 +15,15 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => {
test('no errors on correct usage', async () => {
const { expectErrors } = await setup();
await expectErrors('from a_index | stats by textField', []);
await expectErrors('from a_index | stats by stringField', []);
await expectErrors(
`FROM index
| EVAL doubleField * 3.281
| STATS avg_doubleField = AVG(\`doubleField * 3.281\`)`,
| EVAL numberField * 3.281
| STATS avg_numberField = AVG(\`numberField * 3.281\`)`,
[]
);
await expectErrors(
`FROM index | STATS AVG(doubleField) by round(doubleField) + 1 | EVAL \`round(doubleField) + 1\` / 2`,
`FROM index | STATS AVG(numberField) by round(numberField) + 1 | EVAL \`round(numberField) + 1\` / 2`,
[]
);
});
@ -40,18 +40,18 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => {
test('no errors on correct usage', async () => {
const { expectErrors } = await setup();
await expectErrors('from a_index | stats avg(doubleField) by 1', []);
await expectErrors('from a_index | stats count(`doubleField`)', []);
await expectErrors('from a_index | stats avg(numberField) by 1', []);
await expectErrors('from a_index | stats count(`numberField`)', []);
await expectErrors('from a_index | stats count(*)', []);
await expectErrors('from a_index | stats count()', []);
await expectErrors('from a_index | stats var0 = count(*)', []);
await expectErrors('from a_index | stats var0 = count()', []);
await expectErrors('from a_index | stats var0 = avg(doubleField), count(*)', []);
await expectErrors('from a_index | stats var0 = avg(numberField), count(*)', []);
await expectErrors(`from a_index | stats sum(case(false, 0, 1))`, []);
await expectErrors(`from a_index | stats var0 = sum( case(false, 0, 1))`, []);
// "or" must accept "null"
await expectErrors('from a_index | stats count(textField == "a" or null)', []);
await expectErrors('from a_index | stats count(stringField == "a" or null)', []);
});
test('sub-command can reference aggregated field', async () => {
@ -59,9 +59,9 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => {
for (const subCommand of ['keep', 'drop', 'eval']) {
await expectErrors(
'from a_index | stats count(`doubleField`) | ' +
'from a_index | stats count(`numberField`) | ' +
subCommand +
' `count(``doubleField``)` ',
' `count(``numberField``)` ',
[]
);
}
@ -70,64 +70,64 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => {
test('errors on agg and non-agg mix', async () => {
const { expectErrors } = await setup();
await expectErrors('from a_index | STATS sum( doubleField ) + abs( doubleField ) ', [
'Cannot combine aggregation and non-aggregation values in [STATS], found [sum(doubleField)+abs(doubleField)]',
await expectErrors('from a_index | STATS sum( numberField ) + abs( numberField ) ', [
'Cannot combine aggregation and non-aggregation values in [STATS], found [sum(numberField)+abs(numberField)]',
]);
await expectErrors('from a_index | STATS abs( doubleField + sum( doubleField )) ', [
'Cannot combine aggregation and non-aggregation values in [STATS], found [abs(doubleField+sum(doubleField))]',
await expectErrors('from a_index | STATS abs( numberField + sum( numberField )) ', [
'Cannot combine aggregation and non-aggregation values in [STATS], found [abs(numberField+sum(numberField))]',
]);
});
test('errors on each aggregation field, which does not contain at least one agg function', async () => {
const { expectErrors } = await setup();
await expectErrors('from a_index | stats doubleField + 1', [
'At least one aggregation function required in [STATS], found [doubleField+1]',
await expectErrors('from a_index | stats numberField + 1', [
'At least one aggregation function required in [STATS], found [numberField+1]',
]);
await expectErrors('from a_index | stats doubleField + 1, textField', [
'At least one aggregation function required in [STATS], found [doubleField+1]',
'Expected an aggregate function or group but got [textField] of type [FieldAttribute]',
await expectErrors('from a_index | stats numberField + 1, stringField', [
'At least one aggregation function required in [STATS], found [numberField+1]',
'Expected an aggregate function or group but got [stringField] of type [FieldAttribute]',
]);
await expectErrors('from a_index | stats doubleField + 1, doubleField + 2, count()', [
'At least one aggregation function required in [STATS], found [doubleField+1]',
'At least one aggregation function required in [STATS], found [doubleField+2]',
await expectErrors('from a_index | stats numberField + 1, numberField + 2, count()', [
'At least one aggregation function required in [STATS], found [numberField+1]',
'At least one aggregation function required in [STATS], found [numberField+2]',
]);
await expectErrors(
'from a_index | stats doubleField + 1, doubleField + count(), count()',
['At least one aggregation function required in [STATS], found [doubleField+1]']
'from a_index | stats numberField + 1, numberField + count(), count()',
['At least one aggregation function required in [STATS], found [numberField+1]']
);
await expectErrors('from a_index | stats 5 + doubleField + 1', [
'At least one aggregation function required in [STATS], found [5+doubleField+1]',
await expectErrors('from a_index | stats 5 + numberField + 1', [
'At least one aggregation function required in [STATS], found [5+numberField+1]',
]);
await expectErrors('from a_index | stats doubleField + 1 by ipField', [
'At least one aggregation function required in [STATS], found [doubleField+1]',
await expectErrors('from a_index | stats numberField + 1 by ipField', [
'At least one aggregation function required in [STATS], found [numberField+1]',
]);
});
test('errors when input is not an aggregate function', async () => {
const { expectErrors } = await setup();
await expectErrors('from a_index | stats doubleField ', [
'Expected an aggregate function or group but got [doubleField] of type [FieldAttribute]',
await expectErrors('from a_index | stats numberField ', [
'Expected an aggregate function or group but got [numberField] of type [FieldAttribute]',
]);
});
test('various errors', async () => {
const { expectErrors } = await setup();
await expectErrors('from a_index | stats doubleField=', [
await expectErrors('from a_index | stats numberField=', [
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
]);
await expectErrors('from a_index | stats doubleField=5 by ', [
await expectErrors('from a_index | stats numberField=5 by ', [
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
]);
await expectErrors('from a_index | stats avg(doubleField) by wrongField', [
await expectErrors('from a_index | stats avg(numberField) by wrongField', [
'Unknown column [wrongField]',
]);
await expectErrors('from a_index | stats avg(doubleField) by wrongField + 1', [
await expectErrors('from a_index | stats avg(numberField) by wrongField + 1', [
'Unknown column [wrongField]',
]);
await expectErrors('from a_index | stats avg(doubleField) by var0 = wrongField + 1', [
await expectErrors('from a_index | stats avg(numberField) by var0 = wrongField + 1', [
'Unknown column [wrongField]',
]);
await expectErrors('from a_index | stats var0 = avg(fn(number)), count(*)', [
@ -142,7 +142,7 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => {
'Using wildcards (*) in round is not allowed',
]);
await expectErrors('from a_index | stats count(count(*))', [
`Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [count(*)] of type [long]`,
`Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [count(*)] of type [number]`,
]);
});
});
@ -152,20 +152,20 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => {
const { expectErrors } = await setup();
await expectErrors(
'from a_index | stats avg(doubleField), percentile(doubleField, 50) by ipField',
'from a_index | stats avg(numberField), percentile(numberField, 50) by ipField',
[]
);
await expectErrors(
'from a_index | stats avg(doubleField), percentile(doubleField, 50) BY ipField',
'from a_index | stats avg(numberField), percentile(numberField, 50) BY ipField',
[]
);
await expectErrors(
'from a_index | stats avg(doubleField), percentile(doubleField, 50) + 1 by ipField',
'from a_index | stats avg(numberField), percentile(numberField, 50) + 1 by ipField',
[]
);
for (const op of ['+', '-', '*', '/', '%']) {
await expectErrors(
`from a_index | stats avg(doubleField) ${op} percentile(doubleField, 50) BY ipField`,
`from a_index | stats avg(numberField) ${op} percentile(numberField, 50) BY ipField`,
[]
);
}
@ -185,7 +185,7 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => {
await expectErrors('from a_index | stats count(* + 1) BY ipField', [
"SyntaxError: no viable alternative at input 'count(* +'",
]);
await expectErrors('from a_index | stats count(* + round(doubleField)) BY ipField', [
await expectErrors('from a_index | stats count(* + round(numberField)) BY ipField', [
"SyntaxError: no viable alternative at input 'count(* +'",
]);
});
@ -197,18 +197,18 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => {
'Using wildcards (*) in round is not allowed',
]);
await expectErrors('from a_index | stats count(count(*)) BY ipField', [
`Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [count(*)] of type [long]`,
`Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [count(*)] of type [number]`,
]);
});
test('various errors', async () => {
const { expectErrors } = await setup();
await expectErrors('from a_index | stats avg(doubleField) by percentile(doubleField)', [
await expectErrors('from a_index | stats avg(numberField) by percentile(numberField)', [
'STATS BY does not support function percentile',
]);
await expectErrors(
'from a_index | stats avg(doubleField) by textField, percentile(doubleField) by ipField',
'from a_index | stats avg(numberField) by stringField, percentile(numberField) by ipField',
[
"SyntaxError: mismatched input 'by' expecting <EOF>",
'STATS BY does not support function percentile',
@ -220,37 +220,34 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => {
test('no errors', async () => {
const { expectErrors } = await setup();
await expectErrors('from index | stats by bucket(dateField, pi(), "", "")', []);
await expectErrors(
'from index | stats by bucket(dateField, 1 + 30 / 10, "", "")',
[]
);
await expectErrors(
'from index | stats by bucket(dateField, 1 + 30 / 10, concat("", ""), "")',
['Argument of [bucket] must be [date], found value [concat("","")] type [keyword]']
[]
);
});
test('errors', async () => {
const { expectErrors } = await setup();
await expectErrors('from index | stats by bucket(dateField, pi(), "", "")', [
'Argument of [bucket] must be [integer], found value [pi()] type [double]',
]);
await expectErrors(
'from index | stats by bucket(dateField, abs(doubleField), "", "")',
['Argument of [bucket] must be a constant, received [abs(doubleField)]']
'from index | stats by bucket(dateField, abs(numberField), "", "")',
['Argument of [bucket] must be a constant, received [abs(numberField)]']
);
await expectErrors(
'from index | stats by bucket(dateField, abs(length(doubleField)), "", "")',
['Argument of [bucket] must be a constant, received [abs(length(doubleField))]']
'from index | stats by bucket(dateField, abs(length(numberField)), "", "")',
['Argument of [bucket] must be a constant, received [abs(length(numberField))]']
);
await expectErrors(
'from index | stats by bucket(dateField, doubleField, textField, textField)',
'from index | stats by bucket(dateField, numberField, stringField, stringField)',
[
'Argument of [bucket] must be a constant, received [doubleField]',
'Argument of [bucket] must be a constant, received [textField]',
'Argument of [bucket] must be a constant, received [textField]',
'Argument of [bucket] must be a constant, received [numberField]',
'Argument of [bucket] must be a constant, received [stringField]',
'Argument of [bucket] must be a constant, received [stringField]',
]
);
});
@ -272,11 +269,11 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => {
const { expectErrors } = await setup();
await expectErrors(
`from a_index | stats 5 + avg(doubleField) ${builtinWrapping}`,
`from a_index | stats 5 + avg(numberField) ${builtinWrapping}`,
[]
);
await expectErrors(
`from a_index | stats 5 ${builtinWrapping} + avg(doubleField)`,
`from a_index | stats 5 ${builtinWrapping} + avg(numberField)`,
[]
);
});
@ -284,16 +281,16 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => {
test('errors', async () => {
const { expectErrors } = await setup();
await expectErrors(`from a_index | stats 5 ${builtinWrapping} + doubleField`, [
`At least one aggregation function required in [STATS], found [5${builtinWrapping}+doubleField]`,
await expectErrors(`from a_index | stats 5 ${builtinWrapping} + numberField`, [
`At least one aggregation function required in [STATS], found [5${builtinWrapping}+numberField]`,
]);
await expectErrors(`from a_index | stats 5 + doubleField ${builtinWrapping}`, [
`At least one aggregation function required in [STATS], found [5+doubleField${builtinWrapping}]`,
await expectErrors(`from a_index | stats 5 + numberField ${builtinWrapping}`, [
`At least one aggregation function required in [STATS], found [5+numberField${builtinWrapping}]`,
]);
await expectErrors(
`from a_index | stats 5 + doubleField ${builtinWrapping}, var0 = sum(doubleField)`,
`from a_index | stats 5 + numberField ${builtinWrapping}, var0 = sum(numberField)`,
[
`At least one aggregation function required in [STATS], found [5+doubleField${builtinWrapping}]`,
`At least one aggregation function required in [STATS], found [5+numberField${builtinWrapping}]`,
]
);
});
@ -307,31 +304,31 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => {
const { expectErrors } = await setup();
await expectErrors(
`from a_index | stats ${evalWrapping} sum(doubleField) ${closingWrapping}`,
`from a_index | stats ${evalWrapping} sum(numberField) ${closingWrapping}`,
[]
);
await expectErrors(
`from a_index | stats ${evalWrapping} sum(doubleField) ${closingWrapping} + ${evalWrapping} sum(doubleField) ${closingWrapping}`,
`from a_index | stats ${evalWrapping} sum(numberField) ${closingWrapping} + ${evalWrapping} sum(numberField) ${closingWrapping}`,
[]
);
await expectErrors(
`from a_index | stats ${evalWrapping} sum(doubleField + doubleField) ${closingWrapping}`,
`from a_index | stats ${evalWrapping} sum(numberField + numberField) ${closingWrapping}`,
[]
);
await expectErrors(
`from a_index | stats ${evalWrapping} sum(doubleField + round(doubleField)) ${closingWrapping}`,
`from a_index | stats ${evalWrapping} sum(numberField + round(numberField)) ${closingWrapping}`,
[]
);
await expectErrors(
`from a_index | stats ${evalWrapping} sum(doubleField + round(doubleField)) ${closingWrapping} + ${evalWrapping} sum(doubleField + round(doubleField)) ${closingWrapping}`,
`from a_index | stats ${evalWrapping} sum(numberField + round(numberField)) ${closingWrapping} + ${evalWrapping} sum(numberField + round(numberField)) ${closingWrapping}`,
[]
);
await expectErrors(
`from a_index | stats sum(${evalWrapping} doubleField ${closingWrapping} )`,
`from a_index | stats sum(${evalWrapping} numberField ${closingWrapping} )`,
[]
);
await expectErrors(
`from a_index | stats sum(${evalWrapping} doubleField ${closingWrapping} ) + sum(${evalWrapping} doubleField ${closingWrapping} )`,
`from a_index | stats sum(${evalWrapping} numberField ${closingWrapping} ) + sum(${evalWrapping} numberField ${closingWrapping} )`,
[]
);
});
@ -340,21 +337,21 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => {
const { expectErrors } = await setup();
await expectErrors(
`from a_index | stats ${evalWrapping} doubleField + sum(doubleField) ${closingWrapping}`,
`from a_index | stats ${evalWrapping} numberField + sum(numberField) ${closingWrapping}`,
[
`Cannot combine aggregation and non-aggregation values in [STATS], found [${evalWrapping}doubleField+sum(doubleField)${closingWrapping}]`,
`Cannot combine aggregation and non-aggregation values in [STATS], found [${evalWrapping}numberField+sum(numberField)${closingWrapping}]`,
]
);
await expectErrors(
`from a_index | stats ${evalWrapping} doubleField + sum(doubleField) ${closingWrapping}, var0 = sum(doubleField)`,
`from a_index | stats ${evalWrapping} numberField + sum(numberField) ${closingWrapping}, var0 = sum(numberField)`,
[
`Cannot combine aggregation and non-aggregation values in [STATS], found [${evalWrapping}doubleField+sum(doubleField)${closingWrapping}]`,
`Cannot combine aggregation and non-aggregation values in [STATS], found [${evalWrapping}numberField+sum(numberField)${closingWrapping}]`,
]
);
await expectErrors(
`from a_index | stats var0 = ${evalWrapping} doubleField + sum(doubleField) ${closingWrapping}, var1 = sum(doubleField)`,
`from a_index | stats var0 = ${evalWrapping} numberField + sum(numberField) ${closingWrapping}, var1 = sum(numberField)`,
[
`Cannot combine aggregation and non-aggregation values in [STATS], found [${evalWrapping}doubleField+sum(doubleField)${closingWrapping}]`,
`Cannot combine aggregation and non-aggregation values in [STATS], found [${evalWrapping}numberField+sum(numberField)${closingWrapping}]`,
]
);
});

View file

@ -1,56 +0,0 @@
/*
* 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 { setup } from './helpers';
describe('validation', () => {
describe('command', () => {
test('date_diff', async () => {
const { expectErrors } = await setup();
await expectErrors(
'row var = date_diff("month", "2023-12-02T11:00:00.000Z", "2023-12-02T11:00:00.000Z")',
[]
);
await expectErrors(
'row var = date_diff("mm", "2023-12-02T11:00:00.000Z", "2023-12-02T11:00:00.000Z")',
[]
);
await expectErrors(
'row var = date_diff("bogus", "2023-12-02T11:00:00.000Z", "2023-12-02T11:00:00.000Z")',
[]
);
await expectErrors(
'from a_index | eval date_diff(textField, "2023-12-02T11:00:00.000Z", "2023-12-02T11:00:00.000Z")',
[]
);
await expectErrors(
'from a_index | eval date_diff("month", dateField, "2023-12-02T11:00:00.000Z")',
[]
);
await expectErrors(
'from a_index | eval date_diff("month", "2023-12-02T11:00:00.000Z", dateField)',
[]
);
await expectErrors('from a_index | eval date_diff("month", textField, dateField)', [
'Argument of [date_diff] must be [date], found value [textField] type [text]',
]);
await expectErrors('from a_index | eval date_diff("month", dateField, textField)', [
'Argument of [date_diff] must be [date], found value [textField] type [text]',
]);
await expectErrors(
'from a_index | eval var = date_diff("year", to_datetime(textField), to_datetime(textField))',
[]
);
await expectErrors('from a_index | eval date_diff(doubleField, textField, textField)', [
'Argument of [date_diff] must be [date], found value [textField] type [text]',
'Argument of [date_diff] must be [date], found value [textField] type [text]',
'Argument of [date_diff] must be [keyword], found value [doubleField] type [double]',
]);
});
});
});

View file

@ -23,18 +23,18 @@ test('should allow param inside agg function argument', async () => {
test('allow params in WHERE command expressions', async () => {
const { validate } = await setup();
const res1 = await validate('FROM index | WHERE textField >= ?start');
const res1 = await validate('FROM index | WHERE stringField >= ?start');
const res2 = await validate(`
FROM index
| WHERE textField >= ?start
| WHERE textField <= ?0
| WHERE textField == ?
| WHERE stringField >= ?start
| WHERE stringField <= ?0
| WHERE stringField == ?
`);
const res3 = await validate(`
FROM index
| WHERE textField >= ?start
AND textField <= ?0
AND textField == ?
| WHERE stringField >= ?start
AND stringField <= ?0
AND stringField == ?
`);
expect(res1).toMatchObject({ errors: [], warnings: [] });

View file

@ -76,7 +76,6 @@ import {
import { collapseWrongArgumentTypeMessages, getMaxMinNumberOfParams } from './helpers';
import { getParamAtPosition } from '../autocomplete/helper';
import { METADATA_FIELDS } from '../shared/constants';
import { isStringType } from '../shared/esql_types';
function validateFunctionLiteralArg(
astFunction: ESQLFunction,
@ -880,7 +879,6 @@ function validateColumnForCommand(
if (columnParamsWithInnerTypes.length) {
const hasSomeWrongInnerTypes = columnParamsWithInnerTypes.every(({ innerType }) => {
if (innerType === 'string' && isStringType(columnRef.type)) return false;
return innerType !== 'any' && innerType !== columnRef.type;
});
if (hasSomeWrongInnerTypes) {

View file

@ -13,9 +13,9 @@ import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/publ
describe('getColumnsWithMetadata', () => {
it('should return original columns if fieldsMetadata is not provided', async () => {
const columns = [
{ name: 'ecs.version', type: 'keyword' as DatatableColumnType },
{ name: 'field1', type: 'text' as DatatableColumnType },
{ name: 'field2', type: 'double' as DatatableColumnType },
{ name: 'ecs.version', type: 'string' as DatatableColumnType },
{ name: 'field1', type: 'string' as DatatableColumnType },
{ name: 'field2', type: 'number' as DatatableColumnType },
];
const result = await getColumnsWithMetadata(columns);
@ -24,16 +24,16 @@ describe('getColumnsWithMetadata', () => {
it('should return columns with metadata if both name and type match with ECS fields', async () => {
const columns = [
{ name: 'ecs.field', type: 'text' as DatatableColumnType },
{ name: 'ecs.field', type: 'string' as DatatableColumnType },
{ name: 'ecs.fakeBooleanField', type: 'boolean' as DatatableColumnType },
{ name: 'field2', type: 'double' as DatatableColumnType },
{ name: 'field2', type: 'number' as DatatableColumnType },
];
const fieldsMetadata = {
getClient: jest.fn().mockResolvedValue({
find: jest.fn().mockResolvedValue({
fields: {
'ecs.version': { description: 'ECS version field', type: 'keyword' },
'ecs.field': { description: 'ECS field description', type: 'text' },
'ecs.field': { description: 'ECS field description', type: 'keyword' },
'ecs.fakeBooleanField': {
description: 'ECS fake boolean field description',
type: 'keyword',
@ -48,19 +48,19 @@ describe('getColumnsWithMetadata', () => {
expect(result).toEqual([
{
name: 'ecs.field',
type: 'text',
type: 'string',
metadata: { description: 'ECS field description' },
},
{ name: 'ecs.fakeBooleanField', type: 'boolean' },
{ name: 'field2', type: 'double' },
{ name: 'field2', type: 'number' },
]);
});
it('should handle keyword suffix correctly', async () => {
const columns = [
{ name: 'ecs.version', type: 'keyword' as DatatableColumnType },
{ name: 'ecs.version.keyword', type: 'keyword' as DatatableColumnType },
{ name: 'field2', type: 'double' as DatatableColumnType },
{ name: 'ecs.version', type: 'string' as DatatableColumnType },
{ name: 'ecs.version.keyword', type: 'string' as DatatableColumnType },
{ name: 'field2', type: 'number' as DatatableColumnType },
];
const fieldsMetadata = {
getClient: jest.fn().mockResolvedValue({
@ -75,13 +75,13 @@ describe('getColumnsWithMetadata', () => {
const result = await getColumnsWithMetadata(columns, fieldsMetadata);
expect(result).toEqual([
{ name: 'ecs.version', type: 'keyword', metadata: { description: 'ECS version field' } },
{ name: 'ecs.version', type: 'string', metadata: { description: 'ECS version field' } },
{
name: 'ecs.version.keyword',
type: 'keyword',
type: 'string',
metadata: { description: 'ECS version field' },
},
{ name: 'field2', type: 'double' },
{ name: 'field2', type: 'number' },
]);
});
});

View file

@ -7,6 +7,7 @@
*/
import type { ESQLRealField } from '@kbn/esql-validation-autocomplete';
import { esFieldTypeToKibanaFieldType } from '@kbn/field-types';
import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
import { chunk } from 'lodash';
@ -44,7 +45,11 @@ export async function getColumnsWithMetadata(
const metadata = fields.fields[removeKeywordSuffix(c.name)];
// Need to convert metadata's type (e.g. keyword) to ES|QL type (e.g. string) to check if they are the same
if (!metadata || (metadata?.type && metadata.type !== c.type)) return c;
if (
!metadata ||
(metadata?.type && esFieldTypeToKibanaFieldType(metadata.type) !== c.type)
)
return c;
return {
...c,
metadata: { description: metadata.description },

View file

@ -468,14 +468,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({
undefined,
abortController
).result;
const columns =
table?.columns.map((c) => {
// Casting unsupported as unknown to avoid plethora of warnings
// Remove when addressed https://github.com/elastic/kibana/issues/189666
if (!c.meta.esType || c.meta.esType === 'unsupported')
return { name: c.name, type: 'unknown' };
return { name: c.name, type: c.meta.esType };
}) || [];
const columns = table?.columns.map((c) => ({ name: c.name, type: c.meta.type })) || [];
return await getRateLimitedColumnsWithMetadata(columns, fieldsMetadata);
} catch (e) {
// no action yet

View file

@ -28,6 +28,7 @@
"@kbn/shared-ux-markdown",
"@kbn/fields-metadata-plugin",
"@kbn/esql-validation-autocomplete",
"@kbn/field-types"
],
"exclude": [
"target/**/*",

View file

@ -137,7 +137,6 @@ export default function ({ getService }: FtrProviderContext) {
describe('error messages', () => {
const config = readSetupFromESQLPackage();
const { queryToErrors, indexes, policies } = parseConfig(config);
const missmatches: Array<{ query: string; error: string }> = [];
// Swap these for DEBUG/further investigation on ES bugs
const stringVariants = ['text', 'keyword'] as const;
@ -183,18 +182,11 @@ export default function ({ getService }: FtrProviderContext) {
for (const index of indexes) {
// setup all indexes, mappings and policies here
log.info(
`creating a index "${index}" with mapping...\n${JSON.stringify(config.fields)}`
);
const fieldsExcludingCounterType = config.fields.filter(
// ES|QL supports counter_integer, counter_long, counter_double, date_period, etc.
// but they are not types suitable for Elasticsearch indices
(c: { type: string }) => !c.type.startsWith('counter_') && c.type !== 'date_period'
);
log.info(`creating a index "${index}" with mapping...`);
await es.indices.create(
createIndexRequest(
index,
/unsupported/.test(index) ? config.unsupported_field : fieldsExcludingCounterType,
/unsupported/.test(index) ? config.unsupported_field : config.fields,
stringFieldType,
numberFieldType
),