mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[ES|QL] Improved support for Elasticsearch sub-types in AST for both validation and autocomplete (#189689)
## Summary Fixed version of https://github.com/elastic/kibana/pull/188600 that updates the failed tests [caused by clash with the visitor API tests](https://github.com/elastic/kibana/pull/189516). ### Checklist Delete any items that are not applicable to this PR. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
788e9b34dd
commit
7dca2aa712
41 changed files with 30749 additions and 14414 deletions
|
@ -17,7 +17,7 @@ describe('literal expression', () => {
|
|||
|
||||
expect(literal).toMatchObject({
|
||||
type: 'literal',
|
||||
literalType: 'number',
|
||||
literalType: 'integer',
|
||||
name: '1',
|
||||
value: 1,
|
||||
});
|
||||
|
|
|
@ -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('number', ctx.INTEGER_LITERAL());
|
||||
const literal = createLiteral('integer', ctx.INTEGER_LITERAL());
|
||||
if (literal) {
|
||||
command.args.push(literal);
|
||||
}
|
||||
|
|
|
@ -35,7 +35,9 @@ import type {
|
|||
ESQLCommandMode,
|
||||
ESQLInlineCast,
|
||||
ESQLUnknownItem,
|
||||
ESQLNumericLiteralType,
|
||||
FunctionSubtype,
|
||||
ESQLNumericLiteral,
|
||||
} from './types';
|
||||
|
||||
export function nonNullable<T>(v: T): v is NonNullable<T> {
|
||||
|
@ -87,11 +89,14 @@ export function createList(ctx: ParserRuleContext, values: ESQLLiteral[]): ESQLL
|
|||
};
|
||||
}
|
||||
|
||||
export function createNumericLiteral(ctx: DecimalValueContext | IntegerValueContext): ESQLLiteral {
|
||||
export function createNumericLiteral(
|
||||
ctx: DecimalValueContext | IntegerValueContext,
|
||||
literalType: ESQLNumericLiteralType
|
||||
): ESQLLiteral {
|
||||
const text = ctx.getText();
|
||||
return {
|
||||
type: 'literal',
|
||||
literalType: 'number',
|
||||
literalType,
|
||||
text,
|
||||
name: text,
|
||||
value: Number(text),
|
||||
|
@ -100,10 +105,13 @@ export function createNumericLiteral(ctx: DecimalValueContext | IntegerValueCont
|
|||
};
|
||||
}
|
||||
|
||||
export function createFakeMultiplyLiteral(ctx: ArithmeticUnaryContext): ESQLLiteral {
|
||||
export function createFakeMultiplyLiteral(
|
||||
ctx: ArithmeticUnaryContext,
|
||||
literalType: ESQLNumericLiteralType
|
||||
): ESQLLiteral {
|
||||
return {
|
||||
type: 'literal',
|
||||
literalType: 'number',
|
||||
literalType,
|
||||
text: ctx.getText(),
|
||||
name: ctx.getText(),
|
||||
value: ctx.PLUS() ? 1 : -1,
|
||||
|
@ -158,12 +166,13 @@ export function createLiteral(
|
|||
location: getPosition(node.symbol),
|
||||
incomplete: isMissingText(text),
|
||||
};
|
||||
if (type === 'number') {
|
||||
if (type === 'decimal' || type === 'integer') {
|
||||
return {
|
||||
...partialLiteral,
|
||||
literalType: type,
|
||||
value: Number(text),
|
||||
};
|
||||
paramType: 'number',
|
||||
} as ESQLNumericLiteral<'decimal'> | ESQLNumericLiteral<'integer'>;
|
||||
} else if (type === 'param') {
|
||||
throw new Error('Should never happen');
|
||||
}
|
||||
|
@ -171,7 +180,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 type {
|
||||
import {
|
||||
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));
|
||||
fn.args.push(createFakeMultiplyLiteral(ctx, 'integer'));
|
||||
if (arg) {
|
||||
fn.args.push(arg);
|
||||
}
|
||||
|
@ -328,16 +328,21 @@ 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());
|
||||
return createNumericLiteral(ctx.decimalValue(), 'decimal');
|
||||
}
|
||||
|
||||
// Integer type encompasses integer
|
||||
if (ctx instanceof IntegerLiteralContext) {
|
||||
return createNumericLiteral(ctx.integerValue());
|
||||
return createNumericLiteral(ctx.integerValue(), 'integer');
|
||||
}
|
||||
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 (
|
||||
|
@ -346,14 +351,18 @@ 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!));
|
||||
values.push(createNumericLiteral(value!, isDecimal ? 'decimal' : 'integer'));
|
||||
}
|
||||
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);
|
||||
|
|
|
@ -13,7 +13,7 @@ test('can mint a numeric literal', () => {
|
|||
|
||||
expect(node).toMatchObject({
|
||||
type: 'literal',
|
||||
literalType: 'number',
|
||||
literalType: 'integer',
|
||||
name: '42',
|
||||
value: 42,
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ESQLNumberLiteral } from '../types';
|
||||
import { ESQLDecimalLiteral, ESQLIntegerLiteral, ESQLNumericLiteralType } from '../types';
|
||||
import { AstNodeParserFields, AstNodeTemplate } from './types';
|
||||
|
||||
export class Builder {
|
||||
|
@ -25,16 +25,20 @@ export class Builder {
|
|||
});
|
||||
|
||||
/**
|
||||
* Constructs a number literal node.
|
||||
* Constructs a integer literal node.
|
||||
*/
|
||||
public static readonly numericLiteral = (
|
||||
template: Omit<AstNodeTemplate<ESQLNumberLiteral>, 'literalType' | 'name'>
|
||||
): ESQLNumberLiteral => {
|
||||
const node: ESQLNumberLiteral = {
|
||||
template: Omit<
|
||||
AstNodeTemplate<ESQLIntegerLiteral | ESQLDecimalLiteral>,
|
||||
'literalType' | 'name'
|
||||
>,
|
||||
type: ESQLNumericLiteralType = 'integer'
|
||||
): ESQLIntegerLiteral | ESQLDecimalLiteral => {
|
||||
const node: ESQLIntegerLiteral | ESQLDecimalLiteral = {
|
||||
...template,
|
||||
...Builder.parserFields(template),
|
||||
type: 'literal',
|
||||
literalType: 'number',
|
||||
literalType: type,
|
||||
name: template.value.toString(),
|
||||
};
|
||||
|
||||
|
|
|
@ -179,19 +179,30 @@ export interface ESQLList extends ESQLAstBaseItem {
|
|||
values: ESQLLiteral[];
|
||||
}
|
||||
|
||||
export type ESQLNumericLiteralType = 'decimal' | 'integer';
|
||||
|
||||
export type ESQLLiteral =
|
||||
| ESQLNumberLiteral
|
||||
| ESQLDecimalLiteral
|
||||
| ESQLIntegerLiteral
|
||||
| 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 ESQLNumberLiteral extends ESQLAstBaseItem {
|
||||
export interface ESQLNumericLiteral<T extends ESQLNumericLiteralType> extends ESQLAstBaseItem {
|
||||
type: 'literal';
|
||||
literalType: 'number';
|
||||
literalType: T;
|
||||
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 {
|
||||
|
|
|
@ -18,11 +18,12 @@ import type {
|
|||
ESQLAstNodeWithArgs,
|
||||
ESQLColumn,
|
||||
ESQLCommandOption,
|
||||
ESQLDecimalLiteral,
|
||||
ESQLFunction,
|
||||
ESQLInlineCast,
|
||||
ESQLIntegerLiteral,
|
||||
ESQLList,
|
||||
ESQLLiteral,
|
||||
ESQLNumberLiteral,
|
||||
ESQLSource,
|
||||
ESQLTimeInterval,
|
||||
} from '../types';
|
||||
|
@ -260,10 +261,14 @@ export class LimitCommandVisitorContext<
|
|||
/**
|
||||
* @returns The first numeric literal argument of the command.
|
||||
*/
|
||||
public numericLiteral(): ESQLNumberLiteral | undefined {
|
||||
public numericLiteral(): ESQLIntegerLiteral | ESQLDecimalLiteral | undefined {
|
||||
const arg = firstItem(this.node.args);
|
||||
|
||||
if (arg && arg.type === 'literal' && arg.literalType === 'number') {
|
||||
if (
|
||||
arg &&
|
||||
arg.type === 'literal' &&
|
||||
(arg.literalType === 'integer' || arg.literalType === 'decimal')
|
||||
) {
|
||||
return arg;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -211,7 +211,7 @@ describe('structurally can walk all nodes', () => {
|
|||
expect(columns).toMatchObject([
|
||||
{
|
||||
type: 'literal',
|
||||
literalType: 'number',
|
||||
literalType: 'integer',
|
||||
name: '123',
|
||||
},
|
||||
{
|
||||
|
@ -244,7 +244,7 @@ describe('structurally can walk all nodes', () => {
|
|||
expect(columns).toMatchObject([
|
||||
{
|
||||
type: 'literal',
|
||||
literalType: 'number',
|
||||
literalType: 'integer',
|
||||
name: '1',
|
||||
},
|
||||
{
|
||||
|
@ -264,7 +264,7 @@ describe('structurally can walk all nodes', () => {
|
|||
},
|
||||
{
|
||||
type: 'literal',
|
||||
literalType: 'number',
|
||||
literalType: 'decimal',
|
||||
name: '3.14',
|
||||
},
|
||||
]);
|
||||
|
@ -288,12 +288,12 @@ describe('structurally can walk all nodes', () => {
|
|||
values: [
|
||||
{
|
||||
type: 'literal',
|
||||
literalType: 'number',
|
||||
literalType: 'integer',
|
||||
name: '1',
|
||||
},
|
||||
{
|
||||
type: 'literal',
|
||||
literalType: 'number',
|
||||
literalType: 'integer',
|
||||
name: '2',
|
||||
},
|
||||
],
|
||||
|
@ -318,12 +318,12 @@ describe('structurally can walk all nodes', () => {
|
|||
values: [
|
||||
{
|
||||
type: 'literal',
|
||||
literalType: 'number',
|
||||
literalType: 'integer',
|
||||
name: '1',
|
||||
},
|
||||
{
|
||||
type: 'literal',
|
||||
literalType: 'number',
|
||||
literalType: 'integer',
|
||||
name: '2',
|
||||
},
|
||||
],
|
||||
|
@ -333,7 +333,7 @@ describe('structurally can walk all nodes', () => {
|
|||
values: [
|
||||
{
|
||||
type: 'literal',
|
||||
literalType: 'number',
|
||||
literalType: 'decimal',
|
||||
name: '3.3',
|
||||
},
|
||||
],
|
||||
|
@ -342,17 +342,17 @@ describe('structurally can walk all nodes', () => {
|
|||
expect(literals).toMatchObject([
|
||||
{
|
||||
type: 'literal',
|
||||
literalType: 'number',
|
||||
literalType: 'integer',
|
||||
name: '1',
|
||||
},
|
||||
{
|
||||
type: 'literal',
|
||||
literalType: 'number',
|
||||
literalType: 'integer',
|
||||
name: '2',
|
||||
},
|
||||
{
|
||||
type: 'literal',
|
||||
literalType: 'number',
|
||||
literalType: 'decimal',
|
||||
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::number';
|
||||
const query = 'FROM index | STATS a = 123::integer';
|
||||
const { ast } = getAstAndSyntaxErrors(query);
|
||||
|
||||
const casts: ESQLInlineCast[] = [];
|
||||
|
@ -523,10 +523,10 @@ describe('structurally can walk all nodes', () => {
|
|||
expect(casts).toMatchObject([
|
||||
{
|
||||
type: 'inlineCast',
|
||||
castType: 'number',
|
||||
castType: 'integer',
|
||||
value: {
|
||||
type: 'literal',
|
||||
literalType: 'number',
|
||||
literalType: 'integer',
|
||||
value: 123,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -12,7 +12,6 @@ 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'],
|
||||
|
@ -240,10 +239,10 @@ function getFunctionDefinition(ESFunctionDefinition: Record<string, any>): Funct
|
|||
...signature,
|
||||
params: signature.params.map((param: any) => ({
|
||||
...param,
|
||||
type: esqlToKibanaType(param.type),
|
||||
type: param.type,
|
||||
description: undefined,
|
||||
})),
|
||||
returnType: esqlToKibanaType(signature.returnType),
|
||||
returnType: signature.returnType,
|
||||
variadic: undefined, // we don't support variadic property
|
||||
minParams: signature.variadic
|
||||
? signature.params.filter((param: any) => !param.optional).length
|
||||
|
|
|
@ -25,6 +25,7 @@ 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`;
|
||||
|
||||
|
@ -141,8 +142,8 @@ function generateImplicitDateCastingTestsForFunction(
|
|||
const allSignaturesWithDateParams = definition.signatures.filter((signature) =>
|
||||
signature.params.some(
|
||||
(param, i) =>
|
||||
param.type === 'date' &&
|
||||
!definition.signatures.some((def) => getParamAtPosition(def, i)?.type === 'string') // don't count parameters that already accept a string
|
||||
(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
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -300,8 +301,8 @@ function generateWhereCommandTestsForEvalFunction(
|
|||
// TODO: not sure why there's this constraint...
|
||||
const supportedFunction = signatures.some(
|
||||
({ returnType, params }) =>
|
||||
['number', 'string'].includes(returnType) &&
|
||||
params.every(({ type }) => ['number', 'string'].includes(type))
|
||||
[...ESQL_NUMBER_TYPES, 'string'].includes(returnType) &&
|
||||
params.every(({ type }) => [...ESQL_NUMBER_TYPES, 'string'].includes(type))
|
||||
);
|
||||
|
||||
if (!supportedFunction) {
|
||||
|
@ -311,12 +312,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
|
||||
['number', 'string'].includes(returnType)
|
||||
[...ESQL_NUMBER_TYPES, 'string'].includes(returnType)
|
||||
);
|
||||
for (const { params, returnType, ...restSign } of supportedSignatures) {
|
||||
const correctMapping = getFieldMapping(params);
|
||||
testCases.set(
|
||||
`from a_index | where ${returnType !== 'number' ? 'length(' : ''}${
|
||||
`from a_index | where ${!isNumericType(returnType) ? 'length(' : ''}${
|
||||
// hijacking a bit this function to produce a function call
|
||||
getFunctionSignatures(
|
||||
{
|
||||
|
@ -326,7 +327,7 @@ function generateWhereCommandTestsForEvalFunction(
|
|||
},
|
||||
{ withTypes: false }
|
||||
)[0].declaration
|
||||
}${returnType !== 'number' ? ')' : ''} > 0`,
|
||||
}${!isNumericType(returnType) ? ')' : ''} > 0`,
|
||||
[]
|
||||
);
|
||||
|
||||
|
@ -337,7 +338,7 @@ function generateWhereCommandTestsForEvalFunction(
|
|||
supportedTypesAndFieldNames
|
||||
);
|
||||
testCases.set(
|
||||
`from a_index | where ${returnType !== 'number' ? 'length(' : ''}${
|
||||
`from a_index | where ${!isNumericType(returnType) ? 'length(' : ''}${
|
||||
// hijacking a bit this function to produce a function call
|
||||
getFunctionSignatures(
|
||||
{
|
||||
|
@ -347,7 +348,7 @@ function generateWhereCommandTestsForEvalFunction(
|
|||
},
|
||||
{ withTypes: false }
|
||||
)[0].declaration
|
||||
}${returnType !== 'number' ? ')' : ''} > 0`,
|
||||
}${!isNumericType(returnType) ? ')' : ''} > 0`,
|
||||
expectedErrors
|
||||
);
|
||||
}
|
||||
|
@ -357,7 +358,7 @@ function generateWhereCommandTestsForAggFunction(
|
|||
{ name, alias, signatures, ...defRest }: FunctionDefinition,
|
||||
testCases: Map<string, string[]>
|
||||
) {
|
||||
// statsSignatures.some(({ returnType, params }) => ['number'].includes(returnType))
|
||||
// statsSignatures.some(({ returnType, params }) => [...ESQL_NUMBER_TYPES].includes(returnType))
|
||||
for (const { params, ...signRest } of signatures) {
|
||||
const fieldMapping = getFieldMapping(params);
|
||||
|
||||
|
@ -542,7 +543,7 @@ function generateEvalCommandTestsForEvalFunction(
|
|||
signatureWithGreatestNumberOfParams.params
|
||||
).concat({
|
||||
name: 'extraArg',
|
||||
type: 'number',
|
||||
type: 'integer',
|
||||
});
|
||||
|
||||
// get the expected args from the first signature in case of errors
|
||||
|
@ -660,7 +661,7 @@ function generateStatsCommandTestsForAggFunction(
|
|||
testCases.set(`from a_index | stats var = ${correctSignature}`, []);
|
||||
testCases.set(`from a_index | stats ${correctSignature}`, []);
|
||||
|
||||
if (signRest.returnType === 'number') {
|
||||
if (isNumericType(signRest.returnType)) {
|
||||
testCases.set(`from a_index | stats var = round(${correctSignature})`, []);
|
||||
testCases.set(`from a_index | stats round(${correctSignature})`, []);
|
||||
testCases.set(
|
||||
|
@ -713,8 +714,8 @@ function generateStatsCommandTestsForAggFunction(
|
|||
}
|
||||
|
||||
// test only numeric functions for now
|
||||
if (params[0].type === 'number') {
|
||||
const nestedBuiltin = 'numberField / 2';
|
||||
if (isNumericType(params[0].type)) {
|
||||
const nestedBuiltin = 'doubleField / 2';
|
||||
const fieldMappingWithNestedBuiltinFunctions = getFieldMapping(params);
|
||||
fieldMappingWithNestedBuiltinFunctions[0].name = nestedBuiltin;
|
||||
|
||||
|
@ -726,16 +727,16 @@ function generateStatsCommandTestsForAggFunction(
|
|||
},
|
||||
{ withTypes: false }
|
||||
)[0].declaration;
|
||||
// from a_index | STATS aggFn( numberField / 2 )
|
||||
// from a_index | STATS aggFn( doubleField / 2 )
|
||||
testCases.set(`from a_index | stats ${fnSignatureWithBuiltinString}`, []);
|
||||
testCases.set(`from a_index | stats var0 = ${fnSignatureWithBuiltinString}`, []);
|
||||
testCases.set(`from a_index | stats avg(numberField), ${fnSignatureWithBuiltinString}`, []);
|
||||
testCases.set(`from a_index | stats avg(doubleField), ${fnSignatureWithBuiltinString}`, []);
|
||||
testCases.set(
|
||||
`from a_index | stats avg(numberField), var0 = ${fnSignatureWithBuiltinString}`,
|
||||
`from a_index | stats avg(doubleField), var0 = ${fnSignatureWithBuiltinString}`,
|
||||
[]
|
||||
);
|
||||
|
||||
const nestedEvalAndBuiltin = 'round(numberField / 2)';
|
||||
const nestedEvalAndBuiltin = 'round(doubleField / 2)';
|
||||
const fieldMappingWithNestedEvalAndBuiltinFunctions = getFieldMapping(params);
|
||||
fieldMappingWithNestedBuiltinFunctions[0].name = nestedEvalAndBuiltin;
|
||||
|
||||
|
@ -747,18 +748,18 @@ function generateStatsCommandTestsForAggFunction(
|
|||
},
|
||||
{ withTypes: false }
|
||||
)[0].declaration;
|
||||
// from a_index | STATS aggFn( round(numberField / 2) )
|
||||
// from a_index | STATS aggFn( round(doubleField / 2) )
|
||||
testCases.set(`from a_index | stats ${fnSignatureWithEvalAndBuiltinString}`, []);
|
||||
testCases.set(`from a_index | stats var0 = ${fnSignatureWithEvalAndBuiltinString}`, []);
|
||||
testCases.set(
|
||||
`from a_index | stats avg(numberField), ${fnSignatureWithEvalAndBuiltinString}`,
|
||||
`from a_index | stats avg(doubleField), ${fnSignatureWithEvalAndBuiltinString}`,
|
||||
[]
|
||||
);
|
||||
testCases.set(
|
||||
`from a_index | stats avg(numberField), var0 = ${fnSignatureWithEvalAndBuiltinString}`,
|
||||
`from a_index | stats avg(doubleField), var0 = ${fnSignatureWithEvalAndBuiltinString}`,
|
||||
[]
|
||||
);
|
||||
// from a_index | STATS aggFn(round(numberField / 2) ) BY round(numberField / 2)
|
||||
// from a_index | STATS aggFn(round(doubleField / 2) ) BY round(doubleField / 2)
|
||||
testCases.set(
|
||||
`from a_index | stats ${fnSignatureWithEvalAndBuiltinString} by ${nestedEvalAndBuiltin}`,
|
||||
[]
|
||||
|
@ -768,19 +769,19 @@ function generateStatsCommandTestsForAggFunction(
|
|||
[]
|
||||
);
|
||||
testCases.set(
|
||||
`from a_index | stats avg(numberField), ${fnSignatureWithEvalAndBuiltinString} by ${nestedEvalAndBuiltin}, ipField`,
|
||||
`from a_index | stats avg(doubleField), ${fnSignatureWithEvalAndBuiltinString} by ${nestedEvalAndBuiltin}, ipField`,
|
||||
[]
|
||||
);
|
||||
testCases.set(
|
||||
`from a_index | stats avg(numberField), var0 = ${fnSignatureWithEvalAndBuiltinString} by var1 = ${nestedEvalAndBuiltin}, ipField`,
|
||||
`from a_index | stats avg(doubleField), var0 = ${fnSignatureWithEvalAndBuiltinString} by var1 = ${nestedEvalAndBuiltin}, ipField`,
|
||||
[]
|
||||
);
|
||||
testCases.set(
|
||||
`from a_index | stats avg(numberField), ${fnSignatureWithEvalAndBuiltinString} by ${nestedEvalAndBuiltin}, ${nestedBuiltin}`,
|
||||
`from a_index | stats avg(doubleField), ${fnSignatureWithEvalAndBuiltinString} by ${nestedEvalAndBuiltin}, ${nestedBuiltin}`,
|
||||
[]
|
||||
);
|
||||
testCases.set(
|
||||
`from a_index | stats avg(numberField), var0 = ${fnSignatureWithEvalAndBuiltinString} by var1 = ${nestedEvalAndBuiltin}, ${nestedBuiltin}`,
|
||||
`from a_index | stats avg(doubleField), var0 = ${fnSignatureWithEvalAndBuiltinString} by var1 = ${nestedEvalAndBuiltin}, ${nestedBuiltin}`,
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
@ -798,7 +799,7 @@ function generateStatsCommandTestsForAggFunction(
|
|||
.filter(({ constantOnly }) => !constantOnly)
|
||||
.map(
|
||||
(_) =>
|
||||
`Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]`
|
||||
`Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(doubleField)] of type [double]`
|
||||
);
|
||||
testCases.set(
|
||||
`from a_index | stats var = ${
|
||||
|
@ -965,9 +966,17 @@ function generateSortCommandTestsForAggFunction(
|
|||
const generateSortCommandTestsForGroupingFunction = generateSortCommandTestsForAggFunction;
|
||||
|
||||
const fieldTypesToConstants: Record<SupportedFieldType, string> = {
|
||||
string: '"a"',
|
||||
number: '5',
|
||||
date: 'now()',
|
||||
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")',
|
||||
boolean: 'true',
|
||||
version: 'to_version("1.0.0")',
|
||||
ip: 'to_ip("127.0.0.1")',
|
||||
|
@ -1003,8 +1012,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')!;
|
||||
|
@ -1019,10 +1028,12 @@ 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> = {
|
||||
number: prepareNestedFunction(toInteger),
|
||||
string: prepareNestedFunction(toStringSignature),
|
||||
date: prepareNestedFunction(toDateSignature),
|
||||
double: prepareNestedFunction(toDoubleSignature),
|
||||
integer: prepareNestedFunction(toInteger),
|
||||
text: prepareNestedFunction(toStringSignature),
|
||||
keyword: prepareNestedFunction(toStringSignature),
|
||||
boolean: prepareNestedFunction(toBooleanSignature),
|
||||
ip: prepareNestedFunction(toIpSignature),
|
||||
version: prepareNestedFunction(toVersionSignature),
|
||||
|
@ -1030,6 +1041,8 @@ 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(
|
||||
|
@ -1086,6 +1099,7 @@ function getFieldMapping(
|
|||
number: '5',
|
||||
date: 'now()',
|
||||
};
|
||||
|
||||
return params.map(({ name: _name, type, constantOnly, literalOptions, ...rest }) => {
|
||||
const typeString: string = type;
|
||||
if (isSupportedFieldType(typeString)) {
|
||||
|
@ -1124,7 +1138,7 @@ function getFieldMapping(
|
|||
...rest,
|
||||
};
|
||||
}
|
||||
return { name: 'stringField', type, ...rest };
|
||||
return { name: 'textField', type, ...rest };
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1225,8 +1239,12 @@ function generateIncorrectlyTypedParameters(
|
|||
}
|
||||
const fieldName = wrongFieldMapping[i].name;
|
||||
if (
|
||||
fieldName === 'numberField' &&
|
||||
signatures.every((signature) => getParamAtPosition(signature, i)?.type !== 'string')
|
||||
fieldName === 'doubleField' &&
|
||||
signatures.every(
|
||||
(signature) =>
|
||||
getParamAtPosition(signature, i)?.type !== 'keyword' ||
|
||||
getParamAtPosition(signature, i)?.type !== 'text'
|
||||
)
|
||||
) {
|
||||
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: 'number' },
|
||||
{ name: 'kubernetes.something.something', type: 'number' },
|
||||
{ name: 'any#Char$Field', type: 'double' },
|
||||
{ name: 'kubernetes.something.something', type: 'double' },
|
||||
{ name: '@timestamp', type: 'date' },
|
||||
];
|
||||
|
||||
export const enrichFields = [
|
||||
{ name: 'otherField', type: 'string' },
|
||||
{ name: 'yetAnotherField', type: 'number' },
|
||||
{ name: 'otherField', type: 'text' },
|
||||
{ name: 'yetAnotherField', type: 'double' },
|
||||
];
|
||||
|
||||
// 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: 'string' }];
|
||||
return [{ name: 'firstWord', type: 'text' }];
|
||||
}
|
||||
return fields;
|
||||
}),
|
||||
|
|
|
@ -6,8 +6,11 @@
|
|||
* 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,
|
||||
});
|
||||
|
@ -74,51 +77,76 @@ describe('autocomplete.suggest', () => {
|
|||
const { assertSuggestions } = await setup();
|
||||
|
||||
await assertSuggestions('from a | stats by bucket(/', [
|
||||
...getFieldNamesByType(['number', 'date']).map((field) => `${field},`),
|
||||
...getFunctionSignaturesByReturnType('eval', ['date', 'number'], { scalar: true }).map(
|
||||
(s) => ({ ...s, text: `${s.text},` })
|
||||
...getFieldNamesByType([...ESQL_COMMON_NUMERIC_TYPES, 'date']).map(
|
||||
(field) => `${field},`
|
||||
),
|
||||
...getFunctionSignaturesByReturnType('eval', ['date', ...ESQL_COMMON_NUMERIC_TYPES], {
|
||||
scalar: true,
|
||||
}).map((s) => ({ ...s, text: `${s.text},` })),
|
||||
]);
|
||||
|
||||
await assertSuggestions('from a | stats round(/', [
|
||||
...getFunctionSignaturesByReturnType('stats', 'number', { agg: true, grouping: true }),
|
||||
...getFieldNamesByType('number'),
|
||||
...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }, undefined, [
|
||||
'round',
|
||||
]),
|
||||
...getFunctionSignaturesByReturnType('stats', ESQL_NUMERIC_TYPES, {
|
||||
agg: true,
|
||||
grouping: true,
|
||||
}),
|
||||
...getFieldNamesByType(ESQL_NUMERIC_TYPES),
|
||||
...getFunctionSignaturesByReturnType(
|
||||
'eval',
|
||||
ESQL_NUMERIC_TYPES,
|
||||
{ scalar: true },
|
||||
undefined,
|
||||
['round']
|
||||
),
|
||||
]);
|
||||
await assertSuggestions('from a | stats round(round(/', [
|
||||
...getFunctionSignaturesByReturnType('stats', 'number', { agg: true }),
|
||||
...getFieldNamesByType('number'),
|
||||
...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }, undefined, [
|
||||
'round',
|
||||
]),
|
||||
...getFunctionSignaturesByReturnType('stats', ESQL_NUMERIC_TYPES, { agg: true }),
|
||||
...getFieldNamesByType(ESQL_NUMERIC_TYPES),
|
||||
...getFunctionSignaturesByReturnType(
|
||||
'eval',
|
||||
ESQL_NUMERIC_TYPES,
|
||||
{ scalar: true },
|
||||
undefined,
|
||||
['round']
|
||||
),
|
||||
]);
|
||||
await assertSuggestions('from a | stats avg(round(/', [
|
||||
...getFieldNamesByType('number'),
|
||||
...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }, undefined, [
|
||||
'round',
|
||||
]),
|
||||
...getFieldNamesByType(ESQL_NUMERIC_TYPES),
|
||||
...getFunctionSignaturesByReturnType(
|
||||
'eval',
|
||||
ESQL_NUMERIC_TYPES,
|
||||
{ scalar: true },
|
||||
undefined,
|
||||
['round']
|
||||
),
|
||||
]);
|
||||
await assertSuggestions('from a | stats avg(/', [
|
||||
...getFieldNamesByType('number'),
|
||||
...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }),
|
||||
...getFieldNamesByType(ESQL_NUMERIC_TYPES),
|
||||
...getFunctionSignaturesByReturnType('eval', ESQL_NUMERIC_TYPES, { scalar: true }),
|
||||
]);
|
||||
await assertSuggestions('from a | stats round(avg(/', [
|
||||
...getFieldNamesByType('number'),
|
||||
...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }, undefined, [
|
||||
'round',
|
||||
]),
|
||||
...getFieldNamesByType(ESQL_NUMERIC_TYPES),
|
||||
...getFunctionSignaturesByReturnType(
|
||||
'eval',
|
||||
ESQL_NUMERIC_TYPES,
|
||||
{ scalar: true },
|
||||
undefined,
|
||||
['round']
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
test('when typing inside function left paren', async () => {
|
||||
const { assertSuggestions } = await setup();
|
||||
const expected = [
|
||||
...getFieldNamesByType(['number', 'date', 'boolean', 'ip']),
|
||||
...getFunctionSignaturesByReturnType('stats', ['number', 'date', 'boolean', 'ip'], {
|
||||
scalar: true,
|
||||
}),
|
||||
...getFieldNamesByType([...ESQL_COMMON_NUMERIC_TYPES, 'date', 'boolean', 'ip']),
|
||||
...getFunctionSignaturesByReturnType(
|
||||
'stats',
|
||||
[...ESQL_COMMON_NUMERIC_TYPES, 'date', 'boolean', 'ip'],
|
||||
{
|
||||
scalar: true,
|
||||
}
|
||||
),
|
||||
];
|
||||
|
||||
await assertSuggestions('from a | stats a=min(/)', expected);
|
||||
|
@ -130,8 +158,14 @@ describe('autocomplete.suggest', () => {
|
|||
const { assertSuggestions } = await setup();
|
||||
|
||||
await assertSuggestions('from a | stats avg(b/) by stringField', [
|
||||
...getFieldNamesByType('number'),
|
||||
...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }),
|
||||
...getFieldNamesByType('double'),
|
||||
...getFunctionSignaturesByReturnType(
|
||||
'eval',
|
||||
['double', 'integer', 'long', 'unsigned_long'],
|
||||
{
|
||||
scalar: true,
|
||||
}
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -205,10 +239,15 @@ 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 numberField % /', [
|
||||
...getFieldNamesByType('number'),
|
||||
await assertSuggestions('from a | stats avg(b) by integerField % /', [
|
||||
...getFieldNamesByType('integer'),
|
||||
...getFieldNamesByType('double'),
|
||||
...getFieldNamesByType('long'),
|
||||
'`avg(b)`',
|
||||
...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }),
|
||||
...getFunctionSignaturesByReturnType('eval', ['integer', 'double', 'long'], {
|
||||
scalar: true,
|
||||
}),
|
||||
|
||||
...allGroupingFunctions,
|
||||
]);
|
||||
await assertSuggestions('from a | stats avg(b) by var0 = /', [
|
||||
|
@ -226,10 +265,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 numberField % 2 /', [',', '|']);
|
||||
await assertSuggestions('from a | stats avg(b) by doubleField % 2 /', [',', '|']);
|
||||
|
||||
await assertSuggestions(
|
||||
'from a | stats var0 = AVG(products.base_price) BY var1 = BUCKET(order_date, 1 day)/',
|
||||
'from a | stats var0 = AVG(doubleField) BY var1 = BUCKET(dateField, 1 day)/',
|
||||
[',', '|', '+ $0', '- $0']
|
||||
);
|
||||
});
|
||||
|
|
|
@ -41,7 +41,7 @@ export const triggerCharacters = [',', '(', '=', ' '];
|
|||
export const fields: Array<{ name: string; type: string; suggestedAs?: string }> = [
|
||||
...[
|
||||
'string',
|
||||
'number',
|
||||
'double',
|
||||
'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: 'number', suggestedAs: '`any#Char$Field`' },
|
||||
{ name: 'kubernetes.something.something', type: 'number' },
|
||||
{ name: 'any#Char$Field', type: 'double', suggestedAs: '`any#Char$Field`' },
|
||||
{ name: 'kubernetes.something.something', type: 'double' },
|
||||
];
|
||||
|
||||
export const indexes = (
|
||||
|
|
|
@ -10,15 +10,10 @@ 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,
|
||||
TIME_SYSTEM_PARAMS,
|
||||
} from './factories';
|
||||
import { getSafeInsertText, getUnitDuration, TRIGGER_SUGGESTION_COMMAND } from './factories';
|
||||
import { camelCase, partition } from 'lodash';
|
||||
import { getAstAndSyntaxErrors } from '@kbn/esql-ast';
|
||||
import { FunctionParameter } from '../definitions/types';
|
||||
import { FunctionParameter, FunctionReturnType } from '../definitions/types';
|
||||
import { getParamAtPosition } from './helper';
|
||||
import { nonNullable } from '../shared/helpers';
|
||||
import {
|
||||
|
@ -31,9 +26,16 @@ 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 = [
|
||||
|
@ -166,25 +168,18 @@ describe('autocomplete', () => {
|
|||
['string']
|
||||
),
|
||||
]);
|
||||
testSuggestions('from a | where stringField >= ', [
|
||||
...getFieldNamesByType('string'),
|
||||
...getFunctionSignaturesByReturnType('where', 'string', { scalar: true }),
|
||||
testSuggestions('from a | where textField >= ', [
|
||||
...getFieldNamesByType('any'),
|
||||
...getFunctionSignaturesByReturnType('where', ['any'], { 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 stringField >= stringField ', [
|
||||
'|',
|
||||
...getFunctionSignaturesByReturnType(
|
||||
'where',
|
||||
'boolean',
|
||||
{
|
||||
builtin: true,
|
||||
},
|
||||
['boolean']
|
||||
),
|
||||
testSuggestions('from a | where textField >= textField', [
|
||||
...getFieldNamesByType('any'),
|
||||
...getFunctionSignaturesByReturnType('where', 'any', { scalar: true }),
|
||||
]);
|
||||
testSuggestions.skip('from a | where stringField =~ stringField ', [
|
||||
'|',
|
||||
|
@ -202,52 +197,60 @@ describe('autocomplete', () => {
|
|||
...getFieldNamesByType('any'),
|
||||
...getFunctionSignaturesByReturnType('where', 'any', { scalar: true }),
|
||||
]);
|
||||
testSuggestions(`from a | where stringField >= stringField ${op} numberField `, [
|
||||
...getFunctionSignaturesByReturnType('where', 'boolean', { builtin: true }, ['number']),
|
||||
testSuggestions(`from a | where stringField >= stringField ${op} doubleField `, [
|
||||
...getFunctionSignaturesByReturnType('where', 'boolean', { builtin: true }, ['double']),
|
||||
]);
|
||||
testSuggestions(`from a | where stringField >= stringField ${op} numberField == `, [
|
||||
...getFieldNamesByType('number'),
|
||||
...getFunctionSignaturesByReturnType('where', 'number', { scalar: true }),
|
||||
testSuggestions(`from a | where stringField >= stringField ${op} doubleField == `, [
|
||||
...getFieldNamesByType(ESQL_NUMERIC_TYPES),
|
||||
...getFunctionSignaturesByReturnType('where', ESQL_COMMON_NUMERIC_TYPES, { scalar: true }),
|
||||
]);
|
||||
}
|
||||
testSuggestions('from a | stats a=avg(numberField) | where a ', [
|
||||
testSuggestions('from a | stats a=avg(doubleField) | where a ', [
|
||||
...getFunctionSignaturesByReturnType('where', 'any', { builtin: true, skipAssign: true }, [
|
||||
'number',
|
||||
'double',
|
||||
]),
|
||||
]);
|
||||
// Mind this test: suggestion is aware of previous commands when checking for fields
|
||||
// in this case the numberField has been wiped by the STATS command and suggest cannot find it's type
|
||||
// in this case the doubleField 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(numberField) | where numberField ',
|
||||
'from a | stats a=avg(doubleField) | where doubleField ',
|
||||
[],
|
||||
undefined,
|
||||
undefined,
|
||||
// make the fields suggest aware of the previous STATS, leave the other callbacks untouched
|
||||
[[{ name: 'a', type: 'number' }], undefined, undefined]
|
||||
[[{ name: 'a', type: 'double' }], 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('number'),
|
||||
...getFunctionSignaturesByReturnType('where', 'number', { scalar: true }, undefined, [
|
||||
'log10',
|
||||
]),
|
||||
...getFieldNamesByType(ESQL_NUMERIC_TYPES),
|
||||
...getFunctionSignaturesByReturnType(
|
||||
'where',
|
||||
ESQL_NUMERIC_TYPES,
|
||||
{ scalar: true },
|
||||
undefined,
|
||||
['log10']
|
||||
),
|
||||
],
|
||||
'('
|
||||
);
|
||||
testSuggestions('from a | where log10(numberField) ', [
|
||||
...getFunctionSignaturesByReturnType('where', 'number', { builtin: true }, ['number']),
|
||||
...getFunctionSignaturesByReturnType('where', 'boolean', { builtin: true }, ['number']),
|
||||
testSuggestions('from a | where log10(doubleField) ', [
|
||||
...getFunctionSignaturesByReturnType('where', 'double', { builtin: true }, ['double']),
|
||||
...getFunctionSignaturesByReturnType('where', 'boolean', { builtin: true }, ['double']),
|
||||
]);
|
||||
testSuggestions(
|
||||
'from a | WHERE pow(numberField, )',
|
||||
'from a | WHERE pow(doubleField, )',
|
||||
[
|
||||
...getFieldNamesByType('number'),
|
||||
...getFunctionSignaturesByReturnType('where', 'number', { scalar: true }, undefined, [
|
||||
'pow',
|
||||
]),
|
||||
...getFieldNamesByType(ESQL_NUMERIC_TYPES),
|
||||
...getFunctionSignaturesByReturnType(
|
||||
'where',
|
||||
ESQL_NUMERIC_TYPES,
|
||||
{ scalar: true },
|
||||
undefined,
|
||||
['pow']
|
||||
),
|
||||
],
|
||||
','
|
||||
);
|
||||
|
@ -258,34 +261,34 @@ describe('autocomplete', () => {
|
|||
...getFieldNamesByType('boolean'),
|
||||
...getFunctionSignaturesByReturnType('eval', 'boolean', { scalar: true }),
|
||||
]);
|
||||
testSuggestions('from index | WHERE numberField in ', ['( $0 )']);
|
||||
testSuggestions('from index | WHERE numberField not in ', ['( $0 )']);
|
||||
testSuggestions('from index | WHERE doubleField in ', ['( $0 )']);
|
||||
testSuggestions('from index | WHERE doubleField not in ', ['( $0 )']);
|
||||
testSuggestions(
|
||||
'from index | WHERE numberField not in ( )',
|
||||
'from index | WHERE doubleField not in ( )',
|
||||
[
|
||||
...getFieldNamesByType('number').filter((name) => name !== 'numberField'),
|
||||
...getFunctionSignaturesByReturnType('where', 'number', { scalar: true }),
|
||||
...getFieldNamesByType('double').filter((name) => name !== 'doubleField'),
|
||||
...getFunctionSignaturesByReturnType('where', 'double', { scalar: true }),
|
||||
],
|
||||
'('
|
||||
);
|
||||
testSuggestions(
|
||||
'from index | WHERE numberField in ( `any#Char$Field`, )',
|
||||
'from index | WHERE doubleField in ( `any#Char$Field`, )',
|
||||
[
|
||||
...getFieldNamesByType('number').filter(
|
||||
(name) => name !== '`any#Char$Field`' && name !== 'numberField'
|
||||
...getFieldNamesByType('double').filter(
|
||||
(name) => name !== '`any#Char$Field`' && name !== 'doubleField'
|
||||
),
|
||||
...getFunctionSignaturesByReturnType('where', 'number', { scalar: true }),
|
||||
...getFunctionSignaturesByReturnType('where', 'double', { scalar: true }),
|
||||
],
|
||||
undefined,
|
||||
54 // after the first suggestions
|
||||
);
|
||||
testSuggestions(
|
||||
'from index | WHERE numberField not in ( `any#Char$Field`, )',
|
||||
'from index | WHERE doubleField not in ( `any#Char$Field`, )',
|
||||
[
|
||||
...getFieldNamesByType('number').filter(
|
||||
(name) => name !== '`any#Char$Field`' && name !== 'numberField'
|
||||
...getFieldNamesByType('double').filter(
|
||||
(name) => name !== '`any#Char$Field`' && name !== 'doubleField'
|
||||
),
|
||||
...getFunctionSignaturesByReturnType('where', 'number', { scalar: true }),
|
||||
...getFunctionSignaturesByReturnType('where', 'double', { scalar: true }),
|
||||
],
|
||||
undefined,
|
||||
58 // after the first suggestions
|
||||
|
@ -377,14 +380,14 @@ describe('autocomplete', () => {
|
|||
);
|
||||
|
||||
testSuggestions(
|
||||
`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} `,
|
||||
`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} `,
|
||||
[
|
||||
...getFieldNamesByType('any'),
|
||||
'`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`',
|
||||
'`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`',
|
||||
]
|
||||
);
|
||||
});
|
||||
|
@ -413,7 +416,7 @@ describe('autocomplete', () => {
|
|||
testSuggestions(`from a ${prevCommand}| enrich policy `, ['ON $0', 'WITH $0', '|']);
|
||||
testSuggestions(`from a ${prevCommand}| enrich policy on `, [
|
||||
'stringField',
|
||||
'numberField',
|
||||
'doubleField',
|
||||
'dateField',
|
||||
'booleanField',
|
||||
'ipField',
|
||||
|
@ -466,25 +469,25 @@ describe('autocomplete', () => {
|
|||
...getFieldNamesByType('any'),
|
||||
...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }),
|
||||
]);
|
||||
testSuggestions('from a | eval numberField ', [
|
||||
testSuggestions('from a | eval doubleField ', [
|
||||
...getFunctionSignaturesByReturnType('eval', 'any', { builtin: true, skipAssign: true }, [
|
||||
'number',
|
||||
'double',
|
||||
]),
|
||||
',',
|
||||
'|',
|
||||
]);
|
||||
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 numberField in ', ['( $0 )']);
|
||||
testSuggestions('from index | EVAL doubleField in ', ['( $0 )']);
|
||||
testSuggestions(
|
||||
'from index | EVAL numberField in ( )',
|
||||
'from index | EVAL doubleField in ( )',
|
||||
[
|
||||
...getFieldNamesByType('number').filter((name) => name !== 'numberField'),
|
||||
...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }),
|
||||
...getFieldNamesByType('double').filter((name) => name !== 'doubleField'),
|
||||
...getFunctionSignaturesByReturnType('eval', 'double', { scalar: true }),
|
||||
],
|
||||
'('
|
||||
);
|
||||
testSuggestions('from index | EVAL numberField not in ', ['( $0 )']);
|
||||
testSuggestions('from index | EVAL doubleField not in ', ['( $0 )']);
|
||||
testSuggestions('from index | EVAL not ', [
|
||||
...getFieldNamesByType('boolean'),
|
||||
...getFunctionSignaturesByReturnType('eval', 'boolean', { scalar: true }),
|
||||
|
@ -492,10 +495,10 @@ describe('autocomplete', () => {
|
|||
testSuggestions('from a | eval a=', [
|
||||
...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }),
|
||||
]);
|
||||
testSuggestions('from a | eval a=abs(numberField), b= ', [
|
||||
testSuggestions('from a | eval a=abs(doubleField), b= ', [
|
||||
...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }),
|
||||
]);
|
||||
testSuggestions('from a | eval a=numberField, ', [
|
||||
testSuggestions('from a | eval a=doubleField, ', [
|
||||
'var0 =',
|
||||
...getFieldNamesByType('any'),
|
||||
'a',
|
||||
|
@ -509,10 +512,14 @@ describe('autocomplete', () => {
|
|||
testSuggestions(
|
||||
'from a | eval a=round()',
|
||||
[
|
||||
...getFieldNamesByType('number'),
|
||||
...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }, undefined, [
|
||||
'round',
|
||||
]),
|
||||
...getFieldNamesByType(ESQL_NUMERIC_TYPES),
|
||||
...getFunctionSignaturesByReturnType(
|
||||
'eval',
|
||||
ESQL_NUMERIC_TYPES,
|
||||
{ scalar: true },
|
||||
undefined,
|
||||
['round']
|
||||
),
|
||||
],
|
||||
'('
|
||||
);
|
||||
|
@ -539,64 +546,59 @@ describe('autocomplete', () => {
|
|||
[],
|
||||
' '
|
||||
);
|
||||
testSuggestions('from a | eval a=round(numberField) ', [
|
||||
testSuggestions('from a | eval a=round(doubleField) ', [
|
||||
',',
|
||||
'|',
|
||||
...getFunctionSignaturesByReturnType('eval', 'any', { builtin: true, skipAssign: true }, [
|
||||
'number',
|
||||
'double',
|
||||
]),
|
||||
]);
|
||||
testSuggestions(
|
||||
'from a | eval a=round(numberField, ',
|
||||
'from a | eval a=round(doubleField, ',
|
||||
[
|
||||
...getFieldNamesByType('number'),
|
||||
...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }, undefined, [
|
||||
...getFieldNamesByType('integer'),
|
||||
...getFunctionSignaturesByReturnType('eval', 'integer', { scalar: true }, undefined, [
|
||||
'round',
|
||||
]),
|
||||
],
|
||||
' '
|
||||
);
|
||||
testSuggestions(
|
||||
'from a | eval round(numberField, ',
|
||||
'from a | eval round(doubleField, ',
|
||||
[
|
||||
...getFieldNamesByType('number'),
|
||||
...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }, undefined, [
|
||||
...getFunctionSignaturesByReturnType('eval', 'integer', { scalar: true }, undefined, [
|
||||
'round',
|
||||
]),
|
||||
],
|
||||
' '
|
||||
);
|
||||
testSuggestions('from a | eval a=round(numberField),', [
|
||||
testSuggestions('from a | eval a=round(doubleField),', [
|
||||
'var0 =',
|
||||
...getFieldNamesByType('any'),
|
||||
'a',
|
||||
...getFunctionSignaturesByReturnType('eval', 'any', { 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=round(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=doubleField+ ', [
|
||||
...getFieldNamesByType(ESQL_NUMERIC_TYPES),
|
||||
...getFunctionSignaturesByReturnType('eval', ESQL_COMMON_NUMERIC_TYPES, { scalar: true }),
|
||||
]);
|
||||
testSuggestions('from a | eval a=`any#Char$Field`+ ', [
|
||||
...getFieldNamesByType('number'),
|
||||
'a', // @TODO remove this
|
||||
...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }),
|
||||
...getFieldNamesByType(ESQL_NUMERIC_TYPES),
|
||||
...getFunctionSignaturesByReturnType('eval', ESQL_COMMON_NUMERIC_TYPES, { scalar: true }),
|
||||
]);
|
||||
testSuggestions(
|
||||
'from a | stats avg(numberField) by stringField | eval ',
|
||||
'from a | stats avg(doubleField) by stringField | eval ',
|
||||
[
|
||||
'var0 =',
|
||||
'`avg(numberField)`',
|
||||
'`avg(doubleField)`',
|
||||
...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }),
|
||||
],
|
||||
' ',
|
||||
|
@ -605,32 +607,32 @@ describe('autocomplete', () => {
|
|||
[[], undefined, undefined]
|
||||
);
|
||||
testSuggestions(
|
||||
'from a | eval abs(numberField) + 1 | eval ',
|
||||
'from a | eval abs(doubleField) + 1 | eval ',
|
||||
[
|
||||
'var0 =',
|
||||
...getFieldNamesByType('any'),
|
||||
'`abs(numberField) + 1`',
|
||||
'`abs(doubleField) + 1`',
|
||||
...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }),
|
||||
],
|
||||
' '
|
||||
);
|
||||
testSuggestions(
|
||||
'from a | stats avg(numberField) by stringField | eval ',
|
||||
'from a | stats avg(doubleField) by stringField | eval ',
|
||||
[
|
||||
'var0 =',
|
||||
'`avg(numberField)`',
|
||||
'`avg(doubleField)`',
|
||||
...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }),
|
||||
],
|
||||
' ',
|
||||
undefined,
|
||||
// make aware EVAL of the previous STATS command with the buggy field name from expression
|
||||
[[{ name: 'avg_numberField_', type: 'number' }], undefined, undefined]
|
||||
[[{ name: 'avg_doubleField_', type: 'double' }], undefined, undefined]
|
||||
);
|
||||
testSuggestions(
|
||||
'from a | stats avg(numberField), avg(kubernetes.something.something) by stringField | eval ',
|
||||
'from a | stats avg(doubleField), avg(kubernetes.something.something) by stringField | eval ',
|
||||
[
|
||||
'var0 =',
|
||||
'`avg(numberField)`',
|
||||
'`avg(doubleField)`',
|
||||
'`avg(kubernetes.something.something)`',
|
||||
...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }),
|
||||
],
|
||||
|
@ -639,48 +641,64 @@ describe('autocomplete', () => {
|
|||
// make aware EVAL of the previous STATS command with the buggy field name from expression
|
||||
[
|
||||
[
|
||||
{ name: 'avg_numberField_', type: 'number' },
|
||||
{ name: 'avg_kubernetes.something.something_', type: 'number' },
|
||||
{ name: 'avg_doubleField_', type: 'double' },
|
||||
{ name: 'avg_kubernetes.something.something_', type: 'double' },
|
||||
],
|
||||
undefined,
|
||||
undefined,
|
||||
]
|
||||
);
|
||||
testSuggestions(
|
||||
'from a | eval a=round(numberField), b=round()',
|
||||
'from a | eval a=round(doubleField), b=round()',
|
||||
[
|
||||
...getFieldNamesByType('number'),
|
||||
...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }, undefined, [
|
||||
'round',
|
||||
]),
|
||||
...getFieldNamesByType(ESQL_NUMERIC_TYPES),
|
||||
...getFunctionSignaturesByReturnType(
|
||||
'eval',
|
||||
ESQL_NUMERIC_TYPES,
|
||||
{ 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('string').map((v) => `${v},`),
|
||||
...getFunctionSignaturesByReturnType('eval', 'string', { scalar: true }, undefined, [
|
||||
'concat',
|
||||
]).map((v) => ({ ...v, text: `${v.text},` })),
|
||||
...getFieldNamesByType(['text', 'keyword']).map((v) => `${v},`),
|
||||
...getFunctionSignaturesByReturnType(
|
||||
'eval',
|
||||
['text', 'keyword'],
|
||||
{ scalar: true },
|
||||
undefined,
|
||||
['concat']
|
||||
).map((v) => ({ ...v, text: `${v.text},` })),
|
||||
]);
|
||||
testSuggestions(
|
||||
'from a | eval a=concat(stringField, ',
|
||||
'from a | eval a=concat(textField, ',
|
||||
[
|
||||
...getFieldNamesByType('string'),
|
||||
...getFunctionSignaturesByReturnType('eval', 'string', { scalar: true }, undefined, [
|
||||
'concat',
|
||||
]),
|
||||
...getFieldNamesByType(['text', 'keyword']),
|
||||
...getFunctionSignaturesByReturnType(
|
||||
'eval',
|
||||
['text', 'keyword'],
|
||||
{ scalar: true },
|
||||
undefined,
|
||||
['concat']
|
||||
),
|
||||
],
|
||||
' '
|
||||
);
|
||||
// test that the arg type is correct after minParams
|
||||
testSuggestions(
|
||||
'from a | eval a=cidr_match(ipField, stringField, ',
|
||||
'from a | eval a=cidr_match(ipField, textField, ',
|
||||
[
|
||||
...getFieldNamesByType('string'),
|
||||
...getFunctionSignaturesByReturnType('eval', 'string', { scalar: true }, undefined, [
|
||||
'cidr_match',
|
||||
]),
|
||||
...getFieldNamesByType('text'),
|
||||
...getFunctionSignaturesByReturnType(
|
||||
'eval',
|
||||
['text', 'keyword'],
|
||||
{ scalar: true },
|
||||
undefined,
|
||||
['cidr_match']
|
||||
),
|
||||
],
|
||||
' '
|
||||
);
|
||||
|
@ -694,10 +712,14 @@ describe('autocomplete', () => {
|
|||
testSuggestions(
|
||||
'from a | eval a=cidr_match(ipField, ',
|
||||
[
|
||||
...getFieldNamesByType('string'),
|
||||
...getFunctionSignaturesByReturnType('eval', 'string', { scalar: true }, undefined, [
|
||||
'cidr_match',
|
||||
]),
|
||||
...getFieldNamesByType(['text', 'keyword']),
|
||||
...getFunctionSignaturesByReturnType(
|
||||
'eval',
|
||||
['text', 'keyword'],
|
||||
{ scalar: true },
|
||||
undefined,
|
||||
['cidr_match']
|
||||
),
|
||||
],
|
||||
' '
|
||||
);
|
||||
|
@ -709,10 +731,14 @@ describe('autocomplete', () => {
|
|||
testSuggestions(
|
||||
`from a | eval a=${Array(nesting).fill('round(').join('')}`,
|
||||
[
|
||||
...getFieldNamesByType('number'),
|
||||
...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }, undefined, [
|
||||
'round',
|
||||
]),
|
||||
...getFieldNamesByType(ESQL_NUMERIC_TYPES),
|
||||
...getFunctionSignaturesByReturnType(
|
||||
'eval',
|
||||
ESQL_NUMERIC_TYPES,
|
||||
{ scalar: true },
|
||||
undefined,
|
||||
['round']
|
||||
),
|
||||
],
|
||||
'('
|
||||
);
|
||||
|
@ -720,12 +746,12 @@ describe('autocomplete', () => {
|
|||
|
||||
// Smoke testing for suggestions in previous position than the end of the statement
|
||||
testSuggestions(
|
||||
'from a | eval var0 = abs(numberField) | eval abs(var0)',
|
||||
'from a | eval var0 = abs(doubleField) | eval abs(var0)',
|
||||
[
|
||||
',',
|
||||
'|',
|
||||
...getFunctionSignaturesByReturnType('eval', 'any', { builtin: true, skipAssign: true }, [
|
||||
'number',
|
||||
'double',
|
||||
]),
|
||||
],
|
||||
undefined,
|
||||
|
@ -734,10 +760,14 @@ describe('autocomplete', () => {
|
|||
testSuggestions(
|
||||
'from a | eval var0 = abs(b) | eval abs(var0)',
|
||||
[
|
||||
...getFieldNamesByType('number'),
|
||||
...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }, undefined, [
|
||||
'abs',
|
||||
]),
|
||||
...getFieldNamesByType(ESQL_NUMERIC_TYPES),
|
||||
...getFunctionSignaturesByReturnType(
|
||||
'eval',
|
||||
ESQL_NUMERIC_TYPES,
|
||||
{ scalar: true },
|
||||
undefined,
|
||||
['abs']
|
||||
),
|
||||
],
|
||||
undefined,
|
||||
26 /* b column in abs */
|
||||
|
@ -746,7 +776,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 (fn.name !== 'bucket') {
|
||||
if (!['bucket', 'date_extract', 'date_diff'].includes(fn.name)) {
|
||||
for (const signature of fn.signatures) {
|
||||
signature.params.forEach((param, i) => {
|
||||
if (i < signature.params.length) {
|
||||
|
@ -822,6 +852,23 @@ 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), ' ');
|
||||
|
@ -836,7 +883,7 @@ describe('autocomplete', () => {
|
|||
',',
|
||||
'|',
|
||||
...getFunctionSignaturesByReturnType('eval', 'any', { builtin: true, skipAssign: true }, [
|
||||
'number',
|
||||
'integer',
|
||||
]),
|
||||
],
|
||||
' '
|
||||
|
@ -848,39 +895,20 @@ describe('autocomplete', () => {
|
|||
'time_interval',
|
||||
]),
|
||||
]);
|
||||
testSuggestions(
|
||||
'from a | eval a = 1 day + 2 ',
|
||||
[
|
||||
...dateSuggestions,
|
||||
',',
|
||||
'|',
|
||||
...getFunctionSignaturesByReturnType('eval', 'any', { builtin: true, skipAssign: true }, [
|
||||
'number',
|
||||
]),
|
||||
],
|
||||
' '
|
||||
);
|
||||
testSuggestions('from a | eval a = 1 day + 2 ', [',', '|']);
|
||||
testSuggestions(
|
||||
'from a | eval 1 day + 2 ',
|
||||
[
|
||||
...dateSuggestions,
|
||||
...getFunctionSignaturesByReturnType('eval', 'any', { builtin: true, skipAssign: true }, [
|
||||
'number',
|
||||
'integer',
|
||||
]),
|
||||
],
|
||||
' '
|
||||
);
|
||||
testSuggestions(
|
||||
'from a | eval var0=date_trunc()',
|
||||
[
|
||||
...[...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,
|
||||
],
|
||||
[...getLiteralsByType('time_literal').map((t) => `${t},`)],
|
||||
'('
|
||||
);
|
||||
testSuggestions(
|
||||
|
@ -917,7 +945,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(numberField) ';
|
||||
const statement = 'from a | drop stringField | eval var0 = abs(doubleField) ';
|
||||
const triggerOffset = statement.lastIndexOf(' ');
|
||||
const context = createCompletionContext(statement[triggerOffset]);
|
||||
await suggest(
|
||||
|
@ -933,7 +961,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(numberField) ';
|
||||
const statement = 'from a | drop | eval var0 = abs(doubleField) ';
|
||||
const triggerOffset = statement.lastIndexOf('p') + 1; // drop <here>
|
||||
const context = createCompletionContext(statement[triggerOffset]);
|
||||
await suggest(
|
||||
|
@ -1025,10 +1053,13 @@ describe('autocomplete', () => {
|
|||
testSuggestions(
|
||||
'FROM kibana_sample_data_logs | EVAL TRIM(e)',
|
||||
[
|
||||
...getFieldNamesByType('string'),
|
||||
...getFunctionSignaturesByReturnType('eval', 'string', { scalar: true }, undefined, [
|
||||
'trim',
|
||||
]),
|
||||
...getFunctionSignaturesByReturnType(
|
||||
'eval',
|
||||
['text', 'keyword'],
|
||||
{ scalar: true },
|
||||
undefined,
|
||||
['trim']
|
||||
),
|
||||
],
|
||||
undefined,
|
||||
42
|
||||
|
|
|
@ -16,6 +16,7 @@ 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,
|
||||
|
@ -88,6 +89,7 @@ import {
|
|||
getParamAtPosition,
|
||||
getQueryForFields,
|
||||
getSourcesFromCommands,
|
||||
getSupportedTypesForBinaryOperators,
|
||||
isAggFunctionUsedAlready,
|
||||
removeQuoteForSuggestedSources,
|
||||
} from './helper';
|
||||
|
@ -124,7 +126,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: 'number' });
|
||||
newMap.set(field, { name: field, type: 'double' });
|
||||
}
|
||||
return newMap;
|
||||
}
|
||||
|
@ -732,7 +734,7 @@ async function getExpressionSuggestionsByType(
|
|||
workoutBuiltinOptions(rightArg, references)
|
||||
)
|
||||
);
|
||||
if (nodeArgType === 'number' && isLiteralItem(rightArg)) {
|
||||
if (isNumericType(nodeArgType) && isLiteralItem(rightArg)) {
|
||||
// ... EVAL var = 1 <suggest>
|
||||
suggestions.push(...getCompatibleLiterals(command.name, ['time_literal_unit']));
|
||||
}
|
||||
|
@ -740,7 +742,7 @@ async function getExpressionSuggestionsByType(
|
|||
if (rightArg.args.some(isTimeIntervalItem)) {
|
||||
const lastFnArg = rightArg.args[rightArg.args.length - 1];
|
||||
const lastFnArgType = extractFinalTypeFromArg(lastFnArg, references);
|
||||
if (lastFnArgType === 'number' && isLiteralItem(lastFnArg))
|
||||
if (isNumericType(lastFnArgType) && isLiteralItem(lastFnArg))
|
||||
// ... EVAL var = 1 year + 2 <suggest>
|
||||
suggestions.push(...getCompatibleLiterals(command.name, ['time_literal_unit']));
|
||||
}
|
||||
|
@ -777,7 +779,7 @@ async function getExpressionSuggestionsByType(
|
|||
if (nodeArg.args.some(isTimeIntervalItem)) {
|
||||
const lastFnArg = nodeArg.args[nodeArg.args.length - 1];
|
||||
const lastFnArgType = extractFinalTypeFromArg(lastFnArg, references);
|
||||
if (lastFnArgType === 'number' && isLiteralItem(lastFnArg))
|
||||
if (isNumericType(lastFnArgType) && isLiteralItem(lastFnArg))
|
||||
// ... EVAL var = 1 year + 2 <suggest>
|
||||
suggestions.push(...getCompatibleLiterals(command.name, ['time_literal_unit']));
|
||||
}
|
||||
|
@ -793,7 +795,10 @@ 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', 'number', 'boolean'].includes(argDef.type) && !argDef.values) {
|
||||
if (
|
||||
['string', 'text', 'keyword', 'boolean', ...ESQL_NUMBER_TYPES].includes(argDef.type) &&
|
||||
!argDef.values
|
||||
) {
|
||||
// it can be just literal values (i.e. "string")
|
||||
if (argDef.constantOnly) {
|
||||
// ... | <COMMAND> ... <suggest>
|
||||
|
@ -971,6 +976,7 @@ 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>
|
||||
|
@ -1001,17 +1007,16 @@ 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'
|
||||
: finalType,
|
||||
],
|
||||
finalType === 'boolean' && getFunctionDefinition(nodeArg.name)?.type === 'builtin'
|
||||
? ['any']
|
||||
: supportedTypes,
|
||||
command.name,
|
||||
option?.name,
|
||||
getFieldsByType,
|
||||
|
@ -1321,7 +1326,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) && arg.literalType === 'number') {
|
||||
if (isLiteralItem(arg) && isNumericType(arg.literalType)) {
|
||||
// ... | EVAL fn(2 <suggest>)
|
||||
suggestions.push(
|
||||
...(await getFieldsOrFunctionsSuggestions(
|
||||
|
|
|
@ -22,7 +22,8 @@ import {
|
|||
import { shouldBeQuotedSource, getCommandDefinition, shouldBeQuotedText } from '../shared/helpers';
|
||||
import { buildDocumentation, buildFunctionDocumentation } from './documentation_util';
|
||||
import { DOUBLE_BACKTICK, SINGLE_TICK_REGEX } from '../shared/constants';
|
||||
import type { ESQLRealField } from '../validation/types';
|
||||
import { ESQLRealField } from '../validation/types';
|
||||
import { isNumericType } from '../shared/esql_types';
|
||||
|
||||
const allFunctions = statsAggregationFunctionDefinitions
|
||||
.concat(evalFunctionDefinitions)
|
||||
|
@ -359,7 +360,7 @@ export function getUnitDuration(unit: number = 1) {
|
|||
*/
|
||||
export function getCompatibleLiterals(commandName: string, types: string[], names?: string[]) {
|
||||
const suggestions: SuggestionRawDefinition[] = [];
|
||||
if (types.includes('number')) {
|
||||
if (types.some(isNumericType)) {
|
||||
if (commandName === 'limit') {
|
||||
// suggest 10/100/1000 for limit
|
||||
suggestions.push(...buildConstantsDefinitions(['10', '100', '1000'], ''));
|
||||
|
|
|
@ -80,3 +80,15 @@ 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,15 +7,18 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { FunctionDefinition, FunctionParameterType } from './types';
|
||||
import { ESQL_COMMON_NUMERIC_TYPES, ESQL_NUMBER_TYPES } from '../shared/esql_types';
|
||||
import type { FunctionDefinition, FunctionParameterType, FunctionReturnType } from './types';
|
||||
|
||||
function createNumericAggDefinition({
|
||||
name,
|
||||
description,
|
||||
returnType,
|
||||
args = [],
|
||||
}: {
|
||||
name: string;
|
||||
description: string;
|
||||
returnType?: (numericType: FunctionParameterType) => FunctionReturnType;
|
||||
args?: Array<{
|
||||
name: string;
|
||||
type: FunctionParameterType;
|
||||
|
@ -30,9 +33,9 @@ function createNumericAggDefinition({
|
|||
description,
|
||||
supportedCommands: ['stats', 'metrics'],
|
||||
signatures: [
|
||||
{
|
||||
...ESQL_NUMBER_TYPES.map((numericType) => ({
|
||||
params: [
|
||||
{ name: 'column', type: 'number', noNestingFunctions: true },
|
||||
{ name: 'column', type: numericType, noNestingFunctions: true },
|
||||
...args.map(({ name: paramName, type, constantOnly }) => ({
|
||||
name: paramName,
|
||||
type,
|
||||
|
@ -40,8 +43,8 @@ function createNumericAggDefinition({
|
|||
constantOnly,
|
||||
})),
|
||||
],
|
||||
returnType: 'number',
|
||||
},
|
||||
returnType: returnType ? returnType(numericType) : numericType,
|
||||
})),
|
||||
],
|
||||
examples: [
|
||||
`from index | stats result = ${name}(field${extraParamsExample})`,
|
||||
|
@ -56,18 +59,28 @@ 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',
|
||||
|
@ -78,20 +91,42 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [
|
|||
'Returns the median of each data point’s deviation from the median of the entire sample.',
|
||||
}
|
||||
),
|
||||
},
|
||||
{
|
||||
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 }],
|
||||
returnType: () => 'double' as FunctionReturnType,
|
||||
},
|
||||
]
|
||||
.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', {
|
||||
|
@ -100,13 +135,17 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [
|
|||
type: 'agg',
|
||||
supportedCommands: ['stats', 'metrics'],
|
||||
signatures: [
|
||||
{
|
||||
params: [{ name: 'column', type: 'number', noNestingFunctions: true }],
|
||||
returnType: 'number',
|
||||
},
|
||||
...ESQL_COMMON_NUMERIC_TYPES.map((type) => ({
|
||||
params: [{ name: 'column', type, noNestingFunctions: true }],
|
||||
returnType: type,
|
||||
})),
|
||||
{
|
||||
params: [{ name: 'column', type: 'date', noNestingFunctions: true }],
|
||||
returnType: 'number',
|
||||
returnType: 'date',
|
||||
},
|
||||
{
|
||||
params: [{ name: 'column', type: 'date_period', noNestingFunctions: true }],
|
||||
returnType: 'date_period',
|
||||
},
|
||||
{
|
||||
params: [{ name: 'column', type: 'boolean', noNestingFunctions: true }],
|
||||
|
@ -127,13 +166,17 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [
|
|||
type: 'agg',
|
||||
supportedCommands: ['stats', 'metrics'],
|
||||
signatures: [
|
||||
{
|
||||
params: [{ name: 'column', type: 'number', noNestingFunctions: true }],
|
||||
returnType: 'number',
|
||||
},
|
||||
...ESQL_COMMON_NUMERIC_TYPES.map((type) => ({
|
||||
params: [{ name: 'column', type, noNestingFunctions: true }],
|
||||
returnType: type,
|
||||
})),
|
||||
{
|
||||
params: [{ name: 'column', type: 'date', noNestingFunctions: true }],
|
||||
returnType: 'number',
|
||||
returnType: 'date',
|
||||
},
|
||||
{
|
||||
params: [{ name: 'column', type: 'date_period', noNestingFunctions: true }],
|
||||
returnType: 'date_period',
|
||||
},
|
||||
{
|
||||
params: [{ name: 'column', type: 'boolean', noNestingFunctions: true }],
|
||||
|
@ -166,7 +209,7 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [
|
|||
optional: true,
|
||||
},
|
||||
],
|
||||
returnType: 'number',
|
||||
returnType: 'long',
|
||||
},
|
||||
],
|
||||
examples: [`from index | stats result = count(field)`, `from index | stats count(field)`],
|
||||
|
@ -185,9 +228,14 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [
|
|||
{
|
||||
params: [
|
||||
{ name: 'column', type: 'any', noNestingFunctions: true },
|
||||
{ name: 'precision', type: 'number', noNestingFunctions: true, optional: true },
|
||||
...ESQL_NUMBER_TYPES.map((type) => ({
|
||||
name: 'precision',
|
||||
type,
|
||||
noNestingFunctions: true,
|
||||
optional: true,
|
||||
})),
|
||||
],
|
||||
returnType: 'number',
|
||||
returnType: 'long',
|
||||
},
|
||||
],
|
||||
examples: [
|
||||
|
@ -258,14 +306,14 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [
|
|||
},
|
||||
{
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
type: 'integer',
|
||||
noNestingFunctions: true,
|
||||
optional: false,
|
||||
constantOnly: true,
|
||||
},
|
||||
{
|
||||
name: 'order',
|
||||
type: 'string',
|
||||
type: 'keyword',
|
||||
noNestingFunctions: true,
|
||||
optional: false,
|
||||
constantOnly: true,
|
||||
|
@ -292,23 +340,25 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [
|
|||
),
|
||||
supportedCommands: ['stats', 'metrics'],
|
||||
signatures: [
|
||||
{
|
||||
params: [
|
||||
{
|
||||
name: 'number',
|
||||
type: 'number',
|
||||
noNestingFunctions: true,
|
||||
optional: false,
|
||||
},
|
||||
{
|
||||
name: 'weight',
|
||||
type: 'number',
|
||||
noNestingFunctions: true,
|
||||
optional: false,
|
||||
},
|
||||
],
|
||||
returnType: 'number',
|
||||
},
|
||||
...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(),
|
||||
],
|
||||
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,
|
||||
types: Array<
|
||||
| (FunctionParameterType & FunctionReturnType)
|
||||
| [FunctionParameterType, FunctionParameterType, FunctionReturnType]
|
||||
>,
|
||||
functionSignatures: MathFunctionSignature[],
|
||||
description: string,
|
||||
validate?: FunctionDefinition['validate']
|
||||
): FunctionDefinition {
|
||||
|
@ -24,28 +24,41 @@ function createMathDefinition(
|
|||
description,
|
||||
supportedCommands: ['eval', 'where', 'row', 'stats', 'metrics', 'sort'],
|
||||
supportedOptions: ['by'],
|
||||
signatures: types.map((type) => {
|
||||
if (Array.isArray(type)) {
|
||||
return {
|
||||
params: [
|
||||
{ name: 'left', type: type[0] },
|
||||
{ name: 'right', type: type[1] },
|
||||
],
|
||||
returnType: type[2],
|
||||
};
|
||||
}
|
||||
signatures: functionSignatures.map((functionSignature) => {
|
||||
const [lhs, rhs, result] = functionSignature;
|
||||
return {
|
||||
params: [
|
||||
{ name: 'left', type },
|
||||
{ name: 'right', type },
|
||||
{ name: 'left', type: lhs },
|
||||
{ name: 'right', type: rhs },
|
||||
],
|
||||
returnType: type,
|
||||
returnType: result,
|
||||
};
|
||||
}),
|
||||
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,
|
||||
|
@ -58,6 +71,17 @@ 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,
|
||||
|
@ -66,41 +90,7 @@ function createComparisonDefinition(
|
|||
supportedOptions: ['by'],
|
||||
validate,
|
||||
signatures: [
|
||||
{
|
||||
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',
|
||||
},
|
||||
...commonSignatures,
|
||||
// constant strings okay because of implicit casting for
|
||||
// string to version and ip
|
||||
//
|
||||
|
@ -113,13 +103,13 @@ function createComparisonDefinition(
|
|||
{
|
||||
params: [
|
||||
{ name: 'left', type },
|
||||
{ name: 'right', type: 'string' as const, constantOnly: true },
|
||||
{ name: 'right', type: 'text' as const, constantOnly: true },
|
||||
],
|
||||
returnType: 'boolean' as const,
|
||||
},
|
||||
{
|
||||
params: [
|
||||
{ name: 'right', type: 'string' as const, constantOnly: true },
|
||||
{ name: 'left', type: 'text' as const, constantOnly: true },
|
||||
{ name: 'right', type },
|
||||
],
|
||||
returnType: 'boolean' as const,
|
||||
|
@ -130,31 +120,111 @@ 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(
|
||||
'+',
|
||||
['number', ['date', 'time_literal', 'date'], ['time_literal', 'date', 'date']],
|
||||
addTypeTable,
|
||||
i18n.translate('kbn-esql-validation-autocomplete.esql.definition.addDoc', {
|
||||
defaultMessage: 'Add (+)',
|
||||
})
|
||||
),
|
||||
createMathDefinition(
|
||||
'-',
|
||||
['number', ['date', 'time_literal', 'date'], ['time_literal', 'date', 'date']],
|
||||
subtractTypeTable,
|
||||
i18n.translate('kbn-esql-validation-autocomplete.esql.definition.subtractDoc', {
|
||||
defaultMessage: 'Subtract (-)',
|
||||
})
|
||||
),
|
||||
createMathDefinition(
|
||||
'*',
|
||||
['number'],
|
||||
multiplyTypeTable,
|
||||
i18n.translate('kbn-esql-validation-autocomplete.esql.definition.multiplyDoc', {
|
||||
defaultMessage: 'Multiply (*)',
|
||||
})
|
||||
),
|
||||
createMathDefinition(
|
||||
'/',
|
||||
['number'],
|
||||
divideTypeTable,
|
||||
i18n.translate('kbn-esql-validation-autocomplete.esql.definition.divideDoc', {
|
||||
defaultMessage: 'Divide (/)',
|
||||
}),
|
||||
|
@ -162,7 +232,7 @@ export const mathFunctions: FunctionDefinition[] = [
|
|||
const [left, right] = fnDef.args;
|
||||
const messages = [];
|
||||
if (!Array.isArray(left) && !Array.isArray(right)) {
|
||||
if (right.type === 'literal' && right.literalType === 'number') {
|
||||
if (right.type === 'literal' && isNumericType(right.literalType)) {
|
||||
if (right.value === 0) {
|
||||
messages.push({
|
||||
type: 'warning' as const,
|
||||
|
@ -187,7 +257,7 @@ export const mathFunctions: FunctionDefinition[] = [
|
|||
),
|
||||
createMathDefinition(
|
||||
'%',
|
||||
['number'],
|
||||
modulusTypeTable,
|
||||
i18n.translate('kbn-esql-validation-autocomplete.esql.definition.moduleDoc', {
|
||||
defaultMessage: 'Module (%)',
|
||||
}),
|
||||
|
@ -195,7 +265,7 @@ export const mathFunctions: FunctionDefinition[] = [
|
|||
const [left, right] = fnDef.args;
|
||||
const messages = [];
|
||||
if (!Array.isArray(left) && !Array.isArray(right)) {
|
||||
if (right.type === 'literal' && right.literalType === 'number') {
|
||||
if (right.type === 'literal' && isNumericType(right.literalType)) {
|
||||
if (right.value === 0) {
|
||||
messages.push({
|
||||
type: 'warning' as const,
|
||||
|
@ -244,7 +314,7 @@ const comparisonFunctions: FunctionDefinition[] = [
|
|||
},
|
||||
{
|
||||
params: [
|
||||
{ name: 'right', type: 'string' as const, constantOnly: true },
|
||||
{ name: 'left', type: 'string' as const, constantOnly: true },
|
||||
{ name: 'right', type: 'boolean' as const },
|
||||
],
|
||||
returnType: 'boolean' as const,
|
||||
|
@ -274,7 +344,7 @@ const comparisonFunctions: FunctionDefinition[] = [
|
|||
},
|
||||
{
|
||||
params: [
|
||||
{ name: 'right', type: 'string' as const, constantOnly: true },
|
||||
{ name: 'left', type: 'string' as const, constantOnly: true },
|
||||
{ name: 'right', type: 'boolean' as const },
|
||||
],
|
||||
returnType: 'boolean' as const,
|
||||
|
@ -347,8 +417,15 @@ const likeFunctions: FunctionDefinition[] = [
|
|||
signatures: [
|
||||
{
|
||||
params: [
|
||||
{ name: 'left', type: 'string' as const },
|
||||
{ name: 'right', type: 'string' as const },
|
||||
{ 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 },
|
||||
],
|
||||
returnType: 'boolean',
|
||||
},
|
||||
|
@ -383,17 +460,24 @@ 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: 'number' },
|
||||
|
||||
{ name: 'left', type: 'keyword' },
|
||||
{ name: 'right', type: 'any[]' },
|
||||
],
|
||||
returnType: 'boolean',
|
||||
},
|
||||
{
|
||||
params: [
|
||||
{ name: 'left', type: 'string' },
|
||||
{ name: 'left', type: 'text' },
|
||||
{ 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: 'number', constantOnly: true }],
|
||||
params: [{ name: 'size', type: 'integer', constantOnly: true }],
|
||||
},
|
||||
options: [],
|
||||
modes: [],
|
||||
|
@ -390,6 +390,7 @@ 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 },
|
||||
],
|
||||
|
@ -407,6 +408,7 @@ 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,8 +7,53 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FunctionDefinition } from './types';
|
||||
import { FunctionDefinition, FunctionParameterType, FunctionReturnType } 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',
|
||||
|
@ -21,65 +66,18 @@ export const groupingFunctionDefinitions: FunctionDefinition[] = [
|
|||
supportedCommands: ['stats'],
|
||||
supportedOptions: ['by'],
|
||||
signatures: [
|
||||
{
|
||||
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',
|
||||
},
|
||||
...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,
|
||||
};
|
||||
}),
|
||||
],
|
||||
examples: [
|
||||
'from index | eval hd = bucket(bytes, 1 hour)',
|
||||
|
|
|
@ -8,10 +8,20 @@
|
|||
|
||||
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 = [
|
||||
'number',
|
||||
'double',
|
||||
'unsigned_long',
|
||||
'long',
|
||||
'integer',
|
||||
'counter_integer',
|
||||
'counter_long',
|
||||
'counter_double',
|
||||
'date',
|
||||
'string',
|
||||
'date_period',
|
||||
'text',
|
||||
'keyword',
|
||||
'boolean',
|
||||
'ip',
|
||||
'cartesian_point',
|
||||
|
@ -28,21 +38,43 @@ export type SupportedFieldType = (typeof supportedFieldTypes)[number];
|
|||
|
||||
export type FunctionParameterType =
|
||||
| SupportedFieldType
|
||||
| 'string'
|
||||
| 'null'
|
||||
| 'any'
|
||||
| 'chrono_literal'
|
||||
| 'time_literal'
|
||||
| 'number[]'
|
||||
| 'time_duration'
|
||||
| 'double[]'
|
||||
| 'unsigned_long[]'
|
||||
| 'long[]'
|
||||
| 'integer[]'
|
||||
| 'counter_integer[]'
|
||||
| 'counter_long[]'
|
||||
| 'counter_double[]'
|
||||
| 'string[]'
|
||||
| 'keyword[]'
|
||||
| 'text[]'
|
||||
| 'boolean[]'
|
||||
| 'any[]'
|
||||
| 'date[]';
|
||||
| 'datetime[]'
|
||||
| 'date_period[]';
|
||||
|
||||
export type FunctionReturnType =
|
||||
| 'number'
|
||||
| 'double'
|
||||
| 'unsigned_long'
|
||||
| 'long'
|
||||
| 'integer'
|
||||
| 'int'
|
||||
| 'counter_integer'
|
||||
| 'counter_long'
|
||||
| 'counter_double'
|
||||
| 'date'
|
||||
| 'date_period'
|
||||
| 'time_duration'
|
||||
| 'any'
|
||||
| 'boolean'
|
||||
| 'text'
|
||||
| 'keyword'
|
||||
| 'string'
|
||||
| 'cartesian_point'
|
||||
| 'cartesian_shape'
|
||||
|
|
|
@ -1,44 +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.
|
||||
*/
|
||||
|
||||
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;
|
||||
};
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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 type {
|
||||
import {
|
||||
CommandDefinition,
|
||||
CommandOptionsDefinition,
|
||||
FunctionParameter,
|
||||
|
@ -43,7 +43,7 @@ import type {
|
|||
} from '../definitions/types';
|
||||
import type { ESQLRealField, ESQLVariable, ReferenceMaps } from '../validation/types';
|
||||
import { removeMarkerArgFromArgsList } from './context';
|
||||
import { esqlToKibanaType } from './esql_to_kibana_type';
|
||||
import { isNumericDecimalType } from './esql_types';
|
||||
import type { ReasonTypes } from './types';
|
||||
|
||||
export function nonNullable<T>(v: T): v is NonNullable<T> {
|
||||
|
@ -226,6 +226,14 @@ 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;
|
||||
|
@ -234,7 +242,7 @@ function compareLiteralType(argType: string, item: ESQLLiteral) {
|
|||
}
|
||||
|
||||
// date-type parameters accept string literals because of ES auto-casting
|
||||
return ['string', 'date'].includes(argType);
|
||||
return ['string', 'date', 'date', 'date_period'].includes(argType);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -245,7 +253,14 @@ export function lookupColumn(
|
|||
{ fields, variables }: Pick<ReferenceMaps, 'fields' | 'variables'>
|
||||
): ESQLRealField | ESQLVariable | undefined {
|
||||
const columnName = getQuotedColumnName(column);
|
||||
return fields.get(columnName) || variables.get(columnName)?.[0];
|
||||
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]
|
||||
);
|
||||
}
|
||||
|
||||
const ARRAY_REGEXP = /\[\]$/;
|
||||
|
@ -255,10 +270,19 @@ export function isArrayType(type: string) {
|
|||
}
|
||||
|
||||
const arrayToSingularMap: Map<FunctionParameterType, FunctionParameterType> = new Map([
|
||||
['number[]', 'number'],
|
||||
['date[]', 'date'],
|
||||
['boolean[]', 'boolean'],
|
||||
['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'],
|
||||
['boolean[]', 'boolean'],
|
||||
['any[]', 'any'],
|
||||
]);
|
||||
|
||||
|
@ -407,7 +431,8 @@ export function checkFunctionArgMatchesDefinition(
|
|||
return true;
|
||||
}
|
||||
if (arg.type === 'literal') {
|
||||
return compareLiteralType(argType, arg);
|
||||
const matched = compareLiteralType(argType, arg);
|
||||
return matched;
|
||||
}
|
||||
if (arg.type === 'function') {
|
||||
if (isSupportedFunction(arg.name, parentCommand).supported) {
|
||||
|
@ -428,11 +453,21 @@ 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);
|
||||
return wrappedTypes.some(
|
||||
(ct) =>
|
||||
['any', 'null'].includes(ct) ||
|
||||
argType === ct ||
|
||||
(ct === 'string' && ['text', 'keyword'].includes(argType))
|
||||
);
|
||||
}
|
||||
if (arg.type === 'inlineCast') {
|
||||
// TODO - remove with https://github.com/elastic/kibana/issues/174710
|
||||
return argType === esqlToKibanaType(arg.castType);
|
||||
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))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ function addToVariables(
|
|||
if (isColumnItem(oldArg) && isColumnItem(newArg)) {
|
||||
const newVariable: ESQLVariable = {
|
||||
name: newArg.name,
|
||||
type: 'number' /* fallback to number */,
|
||||
type: 'double' /* 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 || 'number' /* fallback to number */,
|
||||
type: rightHandSideArgType || 'double' /* fallback to number */,
|
||||
location: assignOperation.args[0].location,
|
||||
});
|
||||
}
|
||||
|
@ -125,7 +125,7 @@ function addVariableFromExpression(
|
|||
queryString,
|
||||
expressionOperation.location
|
||||
);
|
||||
const expressionType = 'number';
|
||||
const expressionType = 'double';
|
||||
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[]) => {
|
||||
const assertErrors = (errors: unknown[], expectedErrors: string[], query?: string) => {
|
||||
const errorMessages: string[] = [];
|
||||
for (const error of errors) {
|
||||
if (error && typeof error === 'object') {
|
||||
|
@ -46,7 +46,16 @@ export const setup = async () => {
|
|||
errorMessages.push(String(error));
|
||||
}
|
||||
}
|
||||
expect(errorMessages.sort()).toStrictEqual(expectedErrors.sort());
|
||||
|
||||
try {
|
||||
expect(errorMessages.sort()).toStrictEqual(expectedErrors.sort());
|
||||
} catch (error) {
|
||||
throw Error(`${query}\n
|
||||
Received:
|
||||
'${errorMessages.sort()}'
|
||||
Expected:
|
||||
${expectedErrors.sort()}`);
|
||||
}
|
||||
};
|
||||
|
||||
const expectErrors = async (
|
||||
|
@ -57,9 +66,9 @@ export const setup = async () => {
|
|||
cb: ESQLCallbacks = callbacks
|
||||
) => {
|
||||
const { errors, warnings } = await validateQuery(query, getAstAndSyntaxErrors, opts, cb);
|
||||
assertErrors(errors, expectedErrors);
|
||||
assertErrors(errors, expectedErrors, query);
|
||||
if (expectedWarnings) {
|
||||
assertErrors(warnings, expectedWarnings);
|
||||
assertErrors(warnings, expectedWarnings, query);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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 numberField', ['Unknown index [numberField]']);
|
||||
await expectErrors('metrics doubleField', ['Unknown index [doubleField]']);
|
||||
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(numberField) by 1', []);
|
||||
await expectErrors('metrics a_index count(`numberField`)', []);
|
||||
await expectErrors('metrics a_index avg(doubleField) by 1', []);
|
||||
await expectErrors('metrics a_index count(`doubleField`)', []);
|
||||
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(numberField), count(*)', []);
|
||||
await expectErrors('metrics a_index var0 = avg(doubleField), 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(stringField == "a" or null)', []);
|
||||
await expectErrors('metrics other_index max(numberField) by stringField', []);
|
||||
await expectErrors('metrics a_index count(textField == "a" or null)', []);
|
||||
await expectErrors('metrics other_index max(doubleField) by textField', []);
|
||||
});
|
||||
|
||||
test('syntax errors', async () => {
|
||||
const { expectErrors } = await setup();
|
||||
|
||||
await expectErrors('metrics a_index numberField=', [
|
||||
await expectErrors('metrics a_index doubleField=', [
|
||||
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 numberField=5 by ', [
|
||||
await expectErrors('metrics a_index doubleField=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 numberField + 1', [
|
||||
'At least one aggregation function required in [METRICS], found [numberField+1]',
|
||||
await expectErrors('metrics a_index doubleField + 1', [
|
||||
'At least one aggregation function required in [METRICS], found [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', [
|
||||
'At least one aggregation function required in [METRICS], found [a=doubleField+1]',
|
||||
]);
|
||||
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 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 numberField + 1 by ipField', [
|
||||
'At least one aggregation function required in [METRICS], found [numberField+1]',
|
||||
await expectErrors('metrics a_index doubleField + 1 by ipField', [
|
||||
'At least one aggregation function required in [METRICS], found [doubleField+1]',
|
||||
]);
|
||||
});
|
||||
|
||||
test('errors on agg and non-agg mix', async () => {
|
||||
const { expectErrors } = await setup();
|
||||
|
||||
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 sum( doubleField ) + abs( doubleField ) ', [
|
||||
'Cannot combine aggregation and non-aggregation values in [METRICS], found [sum(doubleField)+abs(doubleField)]',
|
||||
]);
|
||||
await expectErrors('METRICS a_index abs( numberField + sum( numberField )) ', [
|
||||
'Cannot combine aggregation and non-aggregation values in [METRICS], found [abs(numberField+sum(numberField))]',
|
||||
await expectErrors('METRICS a_index abs( doubleField + sum( doubleField )) ', [
|
||||
'Cannot combine aggregation and non-aggregation values in [METRICS], found [abs(doubleField+sum(doubleField))]',
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -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 numberField ', [
|
||||
'Expected an aggregate function or group but got [numberField] of type [FieldAttribute]',
|
||||
await expectErrors('metrics a_index doubleField ', [
|
||||
'Expected an aggregate function or group but got [doubleField] 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(`numberField`) | ' +
|
||||
'metrics a_index count(`doubleField`) | ' +
|
||||
subCommand +
|
||||
' `count(``numberField``)` ',
|
||||
' `count(``doubleField``)` ',
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
@ -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 [number]`,
|
||||
`Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [count(*)] of type [long]`,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
@ -204,21 +204,21 @@ export const validationMetricsCommandTestSuite = (setup: helpers.Setup) => {
|
|||
const { expectErrors } = await setup();
|
||||
|
||||
await expectErrors(
|
||||
'metrics a_index avg(numberField), percentile(numberField, 50) by ipField',
|
||||
'metrics a_index avg(doubleField), percentile(doubleField, 50) by ipField',
|
||||
[]
|
||||
);
|
||||
await expectErrors(
|
||||
'metrics a_index avg(numberField), percentile(numberField, 50) BY ipField',
|
||||
'metrics a_index avg(doubleField), percentile(doubleField, 50) BY ipField',
|
||||
[]
|
||||
);
|
||||
await expectErrors(
|
||||
'metrics a_index avg(numberField), percentile(numberField, 50) + 1 by ipField',
|
||||
'metrics a_index avg(doubleField), percentile(doubleField, 50) + 1 by ipField',
|
||||
[]
|
||||
);
|
||||
await expectErrors('metrics a_index avg(numberField) by stringField | limit 100', []);
|
||||
await expectErrors('metrics a_index avg(doubleField) by textField | limit 100', []);
|
||||
for (const op of ['+', '-', '*', '/', '%']) {
|
||||
await expectErrors(
|
||||
`metrics a_index avg(numberField) ${op} percentile(numberField, 50) BY ipField`,
|
||||
`metrics a_index avg(doubleField) ${op} percentile(doubleField, 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 stringField', [
|
||||
await expectErrors('metrics a_index BY textField', [
|
||||
'Expected an aggregate function or group but got [BY] of type [FieldAttribute]',
|
||||
"SyntaxError: extraneous input 'stringField' expecting <EOF>",
|
||||
"SyntaxError: extraneous input 'textField' 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(numberField)) BY ipField', [
|
||||
await expectErrors('metrics a_index \n count(* + round(doubleField)) 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 [number]`,
|
||||
`Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [count(*)] of type [long]`,
|
||||
]);
|
||||
});
|
||||
|
||||
test('errors on unknown field', async () => {
|
||||
const { expectErrors } = await setup();
|
||||
|
||||
await expectErrors('metrics a_index avg(numberField) by wrongField', [
|
||||
await expectErrors('metrics a_index avg(doubleField) by wrongField', [
|
||||
'Unknown column [wrongField]',
|
||||
]);
|
||||
await expectErrors('metrics a_index avg(numberField) by wrongField + 1', [
|
||||
await expectErrors('metrics a_index avg(doubleField) by wrongField + 1', [
|
||||
'Unknown column [wrongField]',
|
||||
]);
|
||||
await expectErrors('metrics a_index avg(numberField) by var0 = wrongField + 1', [
|
||||
await expectErrors('metrics a_index avg(doubleField) 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(numberField) by percentile(numberField)', [
|
||||
await expectErrors('METRICS a_index avg(doubleField) by percentile(doubleField)', [
|
||||
'METRICS BY does not support function percentile',
|
||||
]);
|
||||
await expectErrors(
|
||||
'METRICS a_index avg(numberField) by stringField, percentile(numberField) by ipField',
|
||||
'METRICS a_index avg(doubleField) by textField, percentile(doubleField) 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 stringField', []);
|
||||
await expectErrors('from a_index | stats by textField', []);
|
||||
await expectErrors(
|
||||
`FROM index
|
||||
| EVAL numberField * 3.281
|
||||
| STATS avg_numberField = AVG(\`numberField * 3.281\`)`,
|
||||
| EVAL doubleField * 3.281
|
||||
| STATS avg_doubleField = AVG(\`doubleField * 3.281\`)`,
|
||||
[]
|
||||
);
|
||||
await expectErrors(
|
||||
`FROM index | STATS AVG(numberField) by round(numberField) + 1 | EVAL \`round(numberField) + 1\` / 2`,
|
||||
`FROM index | STATS AVG(doubleField) by round(doubleField) + 1 | EVAL \`round(doubleField) + 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(numberField) by 1', []);
|
||||
await expectErrors('from a_index | stats count(`numberField`)', []);
|
||||
await expectErrors('from a_index | stats avg(doubleField) by 1', []);
|
||||
await expectErrors('from a_index | stats count(`doubleField`)', []);
|
||||
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(numberField), count(*)', []);
|
||||
await expectErrors('from a_index | stats var0 = avg(doubleField), 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(stringField == "a" or null)', []);
|
||||
await expectErrors('from a_index | stats count(textField == "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(`numberField`) | ' +
|
||||
'from a_index | stats count(`doubleField`) | ' +
|
||||
subCommand +
|
||||
' `count(``numberField``)` ',
|
||||
' `count(``doubleField``)` ',
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
@ -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( numberField ) + abs( numberField ) ', [
|
||||
'Cannot combine aggregation and non-aggregation values in [STATS], found [sum(numberField)+abs(numberField)]',
|
||||
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 abs( numberField + sum( numberField )) ', [
|
||||
'Cannot combine aggregation and non-aggregation values in [STATS], found [abs(numberField+sum(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))]',
|
||||
]);
|
||||
});
|
||||
|
||||
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 numberField + 1', [
|
||||
'At least one aggregation function required in [STATS], found [numberField+1]',
|
||||
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, 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, 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, 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 + 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 + count(), count()',
|
||||
['At least one aggregation function required in [STATS], found [numberField+1]']
|
||||
'from a_index | stats doubleField + 1, doubleField + count(), count()',
|
||||
['At least one aggregation function required in [STATS], found [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 5 + doubleField + 1', [
|
||||
'At least one aggregation function required in [STATS], found [5+doubleField+1]',
|
||||
]);
|
||||
await expectErrors('from a_index | stats numberField + 1 by ipField', [
|
||||
'At least one aggregation function required in [STATS], found [numberField+1]',
|
||||
await expectErrors('from a_index | stats doubleField + 1 by ipField', [
|
||||
'At least one aggregation function required in [STATS], found [doubleField+1]',
|
||||
]);
|
||||
});
|
||||
|
||||
test('errors when input is not an aggregate function', async () => {
|
||||
const { expectErrors } = await setup();
|
||||
|
||||
await expectErrors('from a_index | stats numberField ', [
|
||||
'Expected an aggregate function or group but got [numberField] of type [FieldAttribute]',
|
||||
await expectErrors('from a_index | stats doubleField ', [
|
||||
'Expected an aggregate function or group but got [doubleField] of type [FieldAttribute]',
|
||||
]);
|
||||
});
|
||||
|
||||
test('various errors', async () => {
|
||||
const { expectErrors } = await setup();
|
||||
|
||||
await expectErrors('from a_index | stats numberField=', [
|
||||
await expectErrors('from a_index | stats doubleField=', [
|
||||
"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 numberField=5 by ', [
|
||||
await expectErrors('from a_index | stats doubleField=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(numberField) by wrongField', [
|
||||
await expectErrors('from a_index | stats avg(doubleField) by wrongField', [
|
||||
'Unknown column [wrongField]',
|
||||
]);
|
||||
await expectErrors('from a_index | stats avg(numberField) by wrongField + 1', [
|
||||
await expectErrors('from a_index | stats avg(doubleField) by wrongField + 1', [
|
||||
'Unknown column [wrongField]',
|
||||
]);
|
||||
await expectErrors('from a_index | stats avg(numberField) by var0 = wrongField + 1', [
|
||||
await expectErrors('from a_index | stats avg(doubleField) 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 [number]`,
|
||||
`Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [count(*)] of type [long]`,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
@ -152,20 +152,20 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => {
|
|||
const { expectErrors } = await setup();
|
||||
|
||||
await expectErrors(
|
||||
'from a_index | stats avg(numberField), percentile(numberField, 50) by ipField',
|
||||
'from a_index | stats avg(doubleField), percentile(doubleField, 50) by ipField',
|
||||
[]
|
||||
);
|
||||
await expectErrors(
|
||||
'from a_index | stats avg(numberField), percentile(numberField, 50) BY ipField',
|
||||
'from a_index | stats avg(doubleField), percentile(doubleField, 50) BY ipField',
|
||||
[]
|
||||
);
|
||||
await expectErrors(
|
||||
'from a_index | stats avg(numberField), percentile(numberField, 50) + 1 by ipField',
|
||||
'from a_index | stats avg(doubleField), percentile(doubleField, 50) + 1 by ipField',
|
||||
[]
|
||||
);
|
||||
for (const op of ['+', '-', '*', '/', '%']) {
|
||||
await expectErrors(
|
||||
`from a_index | stats avg(numberField) ${op} percentile(numberField, 50) BY ipField`,
|
||||
`from a_index | stats avg(doubleField) ${op} percentile(doubleField, 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(numberField)) BY ipField', [
|
||||
await expectErrors('from a_index | stats count(* + round(doubleField)) 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 [number]`,
|
||||
`Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [count(*)] of type [long]`,
|
||||
]);
|
||||
});
|
||||
|
||||
test('various errors', async () => {
|
||||
const { expectErrors } = await setup();
|
||||
|
||||
await expectErrors('from a_index | stats avg(numberField) by percentile(numberField)', [
|
||||
await expectErrors('from a_index | stats avg(doubleField) by percentile(doubleField)', [
|
||||
'STATS BY does not support function percentile',
|
||||
]);
|
||||
await expectErrors(
|
||||
'from a_index | stats avg(numberField) by stringField, percentile(numberField) by ipField',
|
||||
'from a_index | stats avg(doubleField) by textField, percentile(doubleField) by ipField',
|
||||
[
|
||||
"SyntaxError: mismatched input 'by' expecting <EOF>",
|
||||
'STATS BY does not support function percentile',
|
||||
|
@ -220,34 +220,37 @@ 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(numberField), "", "")',
|
||||
['Argument of [bucket] must be a constant, received [abs(numberField)]']
|
||||
'from index | stats by bucket(dateField, abs(doubleField), "", "")',
|
||||
['Argument of [bucket] must be a constant, received [abs(doubleField)]']
|
||||
);
|
||||
await expectErrors(
|
||||
'from index | stats by bucket(dateField, abs(length(numberField)), "", "")',
|
||||
['Argument of [bucket] must be a constant, received [abs(length(numberField))]']
|
||||
'from index | stats by bucket(dateField, abs(length(doubleField)), "", "")',
|
||||
['Argument of [bucket] must be a constant, received [abs(length(doubleField))]']
|
||||
);
|
||||
await expectErrors(
|
||||
'from index | stats by bucket(dateField, numberField, stringField, stringField)',
|
||||
'from index | stats by bucket(dateField, doubleField, textField, 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]',
|
||||
'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]',
|
||||
]
|
||||
);
|
||||
});
|
||||
|
@ -269,11 +272,11 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => {
|
|||
const { expectErrors } = await setup();
|
||||
|
||||
await expectErrors(
|
||||
`from a_index | stats 5 + avg(numberField) ${builtinWrapping}`,
|
||||
`from a_index | stats 5 + avg(doubleField) ${builtinWrapping}`,
|
||||
[]
|
||||
);
|
||||
await expectErrors(
|
||||
`from a_index | stats 5 ${builtinWrapping} + avg(numberField)`,
|
||||
`from a_index | stats 5 ${builtinWrapping} + avg(doubleField)`,
|
||||
[]
|
||||
);
|
||||
});
|
||||
|
@ -281,16 +284,16 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => {
|
|||
test('errors', async () => {
|
||||
const { expectErrors } = await setup();
|
||||
|
||||
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 ${builtinWrapping} + doubleField`, [
|
||||
`At least one aggregation function required in [STATS], found [5${builtinWrapping}+doubleField]`,
|
||||
]);
|
||||
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}`, [
|
||||
`At least one aggregation function required in [STATS], found [5+doubleField${builtinWrapping}]`,
|
||||
]);
|
||||
await expectErrors(
|
||||
`from a_index | stats 5 + numberField ${builtinWrapping}, var0 = sum(numberField)`,
|
||||
`from a_index | stats 5 + doubleField ${builtinWrapping}, var0 = sum(doubleField)`,
|
||||
[
|
||||
`At least one aggregation function required in [STATS], found [5+numberField${builtinWrapping}]`,
|
||||
`At least one aggregation function required in [STATS], found [5+doubleField${builtinWrapping}]`,
|
||||
]
|
||||
);
|
||||
});
|
||||
|
@ -304,31 +307,31 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => {
|
|||
const { expectErrors } = await setup();
|
||||
|
||||
await expectErrors(
|
||||
`from a_index | stats ${evalWrapping} sum(numberField) ${closingWrapping}`,
|
||||
`from a_index | stats ${evalWrapping} sum(doubleField) ${closingWrapping}`,
|
||||
[]
|
||||
);
|
||||
await expectErrors(
|
||||
`from a_index | stats ${evalWrapping} sum(numberField) ${closingWrapping} + ${evalWrapping} sum(numberField) ${closingWrapping}`,
|
||||
`from a_index | stats ${evalWrapping} sum(doubleField) ${closingWrapping} + ${evalWrapping} sum(doubleField) ${closingWrapping}`,
|
||||
[]
|
||||
);
|
||||
await expectErrors(
|
||||
`from a_index | stats ${evalWrapping} sum(numberField + numberField) ${closingWrapping}`,
|
||||
`from a_index | stats ${evalWrapping} sum(doubleField + doubleField) ${closingWrapping}`,
|
||||
[]
|
||||
);
|
||||
await expectErrors(
|
||||
`from a_index | stats ${evalWrapping} sum(numberField + round(numberField)) ${closingWrapping}`,
|
||||
`from a_index | stats ${evalWrapping} sum(doubleField + round(doubleField)) ${closingWrapping}`,
|
||||
[]
|
||||
);
|
||||
await expectErrors(
|
||||
`from a_index | stats ${evalWrapping} sum(numberField + round(numberField)) ${closingWrapping} + ${evalWrapping} sum(numberField + round(numberField)) ${closingWrapping}`,
|
||||
`from a_index | stats ${evalWrapping} sum(doubleField + round(doubleField)) ${closingWrapping} + ${evalWrapping} sum(doubleField + round(doubleField)) ${closingWrapping}`,
|
||||
[]
|
||||
);
|
||||
await expectErrors(
|
||||
`from a_index | stats sum(${evalWrapping} numberField ${closingWrapping} )`,
|
||||
`from a_index | stats sum(${evalWrapping} doubleField ${closingWrapping} )`,
|
||||
[]
|
||||
);
|
||||
await expectErrors(
|
||||
`from a_index | stats sum(${evalWrapping} numberField ${closingWrapping} ) + sum(${evalWrapping} numberField ${closingWrapping} )`,
|
||||
`from a_index | stats sum(${evalWrapping} doubleField ${closingWrapping} ) + sum(${evalWrapping} doubleField ${closingWrapping} )`,
|
||||
[]
|
||||
);
|
||||
});
|
||||
|
@ -337,21 +340,21 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => {
|
|||
const { expectErrors } = await setup();
|
||||
|
||||
await expectErrors(
|
||||
`from a_index | stats ${evalWrapping} numberField + sum(numberField) ${closingWrapping}`,
|
||||
`from a_index | stats ${evalWrapping} doubleField + sum(doubleField) ${closingWrapping}`,
|
||||
[
|
||||
`Cannot combine aggregation and non-aggregation values in [STATS], found [${evalWrapping}numberField+sum(numberField)${closingWrapping}]`,
|
||||
`Cannot combine aggregation and non-aggregation values in [STATS], found [${evalWrapping}doubleField+sum(doubleField)${closingWrapping}]`,
|
||||
]
|
||||
);
|
||||
await expectErrors(
|
||||
`from a_index | stats ${evalWrapping} numberField + sum(numberField) ${closingWrapping}, var0 = sum(numberField)`,
|
||||
`from a_index | stats ${evalWrapping} doubleField + sum(doubleField) ${closingWrapping}, var0 = sum(doubleField)`,
|
||||
[
|
||||
`Cannot combine aggregation and non-aggregation values in [STATS], found [${evalWrapping}numberField+sum(numberField)${closingWrapping}]`,
|
||||
`Cannot combine aggregation and non-aggregation values in [STATS], found [${evalWrapping}doubleField+sum(doubleField)${closingWrapping}]`,
|
||||
]
|
||||
);
|
||||
await expectErrors(
|
||||
`from a_index | stats var0 = ${evalWrapping} numberField + sum(numberField) ${closingWrapping}, var1 = sum(numberField)`,
|
||||
`from a_index | stats var0 = ${evalWrapping} doubleField + sum(doubleField) ${closingWrapping}, var1 = sum(doubleField)`,
|
||||
[
|
||||
`Cannot combine aggregation and non-aggregation values in [STATS], found [${evalWrapping}numberField+sum(numberField)${closingWrapping}]`,
|
||||
`Cannot combine aggregation and non-aggregation values in [STATS], found [${evalWrapping}doubleField+sum(doubleField)${closingWrapping}]`,
|
||||
]
|
||||
);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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 stringField >= ?start');
|
||||
const res1 = await validate('FROM index | WHERE textField >= ?start');
|
||||
const res2 = await validate(`
|
||||
FROM index
|
||||
| WHERE stringField >= ?start
|
||||
| WHERE stringField <= ?0
|
||||
| WHERE stringField == ?
|
||||
| WHERE textField >= ?start
|
||||
| WHERE textField <= ?0
|
||||
| WHERE textField == ?
|
||||
`);
|
||||
const res3 = await validate(`
|
||||
FROM index
|
||||
| WHERE stringField >= ?start
|
||||
AND stringField <= ?0
|
||||
AND stringField == ?
|
||||
| WHERE textField >= ?start
|
||||
AND textField <= ?0
|
||||
AND textField == ?
|
||||
`);
|
||||
|
||||
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,6 +76,7 @@ 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,
|
||||
|
@ -879,6 +880,7 @@ 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: 'string' as DatatableColumnType },
|
||||
{ name: 'field1', type: 'string' as DatatableColumnType },
|
||||
{ name: 'field2', type: 'number' as DatatableColumnType },
|
||||
{ name: 'ecs.version', type: 'keyword' as DatatableColumnType },
|
||||
{ name: 'field1', type: 'text' as DatatableColumnType },
|
||||
{ name: 'field2', type: 'double' 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: 'string' as DatatableColumnType },
|
||||
{ name: 'ecs.field', type: 'text' as DatatableColumnType },
|
||||
{ name: 'ecs.fakeBooleanField', type: 'boolean' as DatatableColumnType },
|
||||
{ name: 'field2', type: 'number' as DatatableColumnType },
|
||||
{ name: 'field2', type: 'double' 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: 'keyword' },
|
||||
'ecs.field': { description: 'ECS field description', type: 'text' },
|
||||
'ecs.fakeBooleanField': {
|
||||
description: 'ECS fake boolean field description',
|
||||
type: 'keyword',
|
||||
|
@ -48,19 +48,19 @@ describe('getColumnsWithMetadata', () => {
|
|||
expect(result).toEqual([
|
||||
{
|
||||
name: 'ecs.field',
|
||||
type: 'string',
|
||||
type: 'text',
|
||||
metadata: { description: 'ECS field description' },
|
||||
},
|
||||
{ name: 'ecs.fakeBooleanField', type: 'boolean' },
|
||||
{ name: 'field2', type: 'number' },
|
||||
{ name: 'field2', type: 'double' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle keyword suffix correctly', async () => {
|
||||
const columns = [
|
||||
{ name: 'ecs.version', type: 'string' as DatatableColumnType },
|
||||
{ name: 'ecs.version.keyword', type: 'string' as DatatableColumnType },
|
||||
{ name: 'field2', type: 'number' as DatatableColumnType },
|
||||
{ name: 'ecs.version', type: 'keyword' as DatatableColumnType },
|
||||
{ name: 'ecs.version.keyword', type: 'keyword' as DatatableColumnType },
|
||||
{ name: 'field2', type: 'double' 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: 'string', metadata: { description: 'ECS version field' } },
|
||||
{ name: 'ecs.version', type: 'keyword', metadata: { description: 'ECS version field' } },
|
||||
{
|
||||
name: 'ecs.version.keyword',
|
||||
type: 'string',
|
||||
type: 'keyword',
|
||||
metadata: { description: 'ECS version field' },
|
||||
},
|
||||
{ name: 'field2', type: 'number' },
|
||||
{ name: 'field2', type: 'double' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
*/
|
||||
|
||||
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';
|
||||
|
||||
|
@ -45,11 +44,7 @@ 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 && esFieldTypeToKibanaFieldType(metadata.type) !== c.type)
|
||||
)
|
||||
return c;
|
||||
if (!metadata || (metadata?.type && metadata.type !== c.type)) return c;
|
||||
return {
|
||||
...c,
|
||||
metadata: { description: metadata.description },
|
||||
|
|
|
@ -468,7 +468,14 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({
|
|||
undefined,
|
||||
abortController
|
||||
).result;
|
||||
const columns = table?.columns.map((c) => ({ name: c.name, type: c.meta.type })) || [];
|
||||
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 };
|
||||
}) || [];
|
||||
return await getRateLimitedColumnsWithMetadata(columns, fieldsMetadata);
|
||||
} catch (e) {
|
||||
// no action yet
|
||||
|
|
|
@ -28,7 +28,6 @@
|
|||
"@kbn/shared-ux-markdown",
|
||||
"@kbn/fields-metadata-plugin",
|
||||
"@kbn/esql-validation-autocomplete",
|
||||
"@kbn/field-types"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -137,6 +137,7 @@ 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;
|
||||
|
@ -182,11 +183,18 @@ 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...`);
|
||||
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'
|
||||
);
|
||||
await es.indices.create(
|
||||
createIndexRequest(
|
||||
index,
|
||||
/unsupported/.test(index) ? config.unsupported_field : config.fields,
|
||||
/unsupported/.test(index) ? config.unsupported_field : fieldsExcludingCounterType,
|
||||
stringFieldType,
|
||||
numberFieldType
|
||||
),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue