mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
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:
parent
d9e1223f28
commit
8a60cd4e68
37 changed files with 14365 additions and 30691 deletions
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}),
|
||||
|
|
|
@ -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']
|
||||
);
|
||||
});
|
||||
|
|
|
@ -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 = (
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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'], ''));
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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 point’s 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)`,
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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 },
|
||||
],
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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)',
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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;
|
||||
};
|
|
@ -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])
|
||||
);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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}]`,
|
||||
]
|
||||
);
|
||||
});
|
||||
|
|
|
@ -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]',
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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: [] });
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -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) {
|
||||
|
|
|
@ -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' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
"@kbn/shared-ux-markdown",
|
||||
"@kbn/fields-metadata-plugin",
|
||||
"@kbn/esql-validation-autocomplete",
|
||||
"@kbn/field-types"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -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
|
||||
),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue