mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
# Backport This will backport the following commits from `main` to `8.x`: - [[ES|QL] Restructure validation code, remove command settings (#215056)](https://github.com/elastic/kibana/pull/215056) <!--- Backport version: 9.6.6 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Drew Tate","email":"drew.tate@elastic.co"},"sourceCommit":{"committedDate":"2025-03-20T13:16:34Z","message":"[ES|QL] Restructure validation code, remove command settings (#215056)\n\n## Summary\n\nFollow on to several recent efforts including\nhttps://github.com/elastic/kibana/issues/195418 and\nhttps://github.com/elastic/kibana/pull/213325\n\nThis PR\n- reorganizes validation code to make dependencies clearer and make it\nless overwhelming... it's not perfect but it's better\n- removes the deprecated notion of a command \"setting\" which only ever\napplied to `ENRICH`.\n\nNo regression in `ENRICH` mode validation:\n<img width=\"874\" alt=\"Screenshot 2025-03-18 at 1 04 46 PM\"\nsrc=\"https://github.com/user-attachments/assets/e6639d8a-d129-440f-ac30-64a2ef6ab65c\"\n/>\n\nOr hover\n<img width=\"419\" alt=\"Screenshot 2025-03-18 at 7 43 04 PM\"\nsrc=\"https://github.com/user-attachments/assets/8f9c020c-dcfd-42dc-8e14-4b1c4311457b\"\n/>\n\n\n\n\n\n### Checklist\n\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios\n\n---------\n\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>","sha":"9073b194072cee0ef5290982c05cfdb84662c673","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["technical debt","release_note:skip","Feature:ES|QL","Team:ESQL","backport:version","v9.1.0","v8.19.0"],"title":"[ES|QL] Restructure validation code, remove command settings","number":215056,"url":"https://github.com/elastic/kibana/pull/215056","mergeCommit":{"message":"[ES|QL] Restructure validation code, remove command settings (#215056)\n\n## Summary\n\nFollow on to several recent efforts including\nhttps://github.com/elastic/kibana/issues/195418 and\nhttps://github.com/elastic/kibana/pull/213325\n\nThis PR\n- reorganizes validation code to make dependencies clearer and make it\nless overwhelming... it's not perfect but it's better\n- removes the deprecated notion of a command \"setting\" which only ever\napplied to `ENRICH`.\n\nNo regression in `ENRICH` mode validation:\n<img width=\"874\" alt=\"Screenshot 2025-03-18 at 1 04 46 PM\"\nsrc=\"https://github.com/user-attachments/assets/e6639d8a-d129-440f-ac30-64a2ef6ab65c\"\n/>\n\nOr hover\n<img width=\"419\" alt=\"Screenshot 2025-03-18 at 7 43 04 PM\"\nsrc=\"https://github.com/user-attachments/assets/8f9c020c-dcfd-42dc-8e14-4b1c4311457b\"\n/>\n\n\n\n\n\n### Checklist\n\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios\n\n---------\n\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>","sha":"9073b194072cee0ef5290982c05cfdb84662c673"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/215056","number":215056,"mergeCommit":{"message":"[ES|QL] Restructure validation code, remove command settings (#215056)\n\n## Summary\n\nFollow on to several recent efforts including\nhttps://github.com/elastic/kibana/issues/195418 and\nhttps://github.com/elastic/kibana/pull/213325\n\nThis PR\n- reorganizes validation code to make dependencies clearer and make it\nless overwhelming... it's not perfect but it's better\n- removes the deprecated notion of a command \"setting\" which only ever\napplied to `ENRICH`.\n\nNo regression in `ENRICH` mode validation:\n<img width=\"874\" alt=\"Screenshot 2025-03-18 at 1 04 46 PM\"\nsrc=\"https://github.com/user-attachments/assets/e6639d8a-d129-440f-ac30-64a2ef6ab65c\"\n/>\n\nOr hover\n<img width=\"419\" alt=\"Screenshot 2025-03-18 at 7 43 04 PM\"\nsrc=\"https://github.com/user-attachments/assets/8f9c020c-dcfd-42dc-8e14-4b1c4311457b\"\n/>\n\n\n\n\n\n### Checklist\n\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios\n\n---------\n\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>","sha":"9073b194072cee0ef5290982c05cfdb84662c673"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Drew Tate <drew.tate@elastic.co>
This commit is contained in:
parent
04433e758e
commit
7b1eefddd1
20 changed files with 1453 additions and 1469 deletions
|
@ -12,7 +12,6 @@ export type {
|
|||
FunctionDefinition,
|
||||
CommandDefinition,
|
||||
CommandOptionsDefinition,
|
||||
CommandModeDefinition,
|
||||
Literals,
|
||||
} from './src/definitions/types';
|
||||
export type { ESQLCallbacks } from './src/shared/types';
|
||||
|
@ -51,7 +50,6 @@ export {
|
|||
printFunctionSignature,
|
||||
checkFunctionArgMatchesDefinition as isEqualType,
|
||||
isSourceItem,
|
||||
isSettingItem,
|
||||
isFunctionItem,
|
||||
isOptionItem,
|
||||
isColumnItem,
|
||||
|
@ -61,7 +59,6 @@ export {
|
|||
isAssignmentComplete,
|
||||
isSingleItem,
|
||||
} from './src/shared/helpers';
|
||||
export { ENRICH_MODES } from './src/definitions/settings';
|
||||
export { timeUnits } from './src/definitions/literals';
|
||||
export { aggFunctionDefinitions } from './src/definitions/generated/aggregation_functions';
|
||||
export { getFunctionSignatures } from './src/definitions/helpers';
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
|
||||
import { ESQLCommand } from '@kbn/esql-ast';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ENRICH_MODES } from '../../../definitions/commands_helpers';
|
||||
import { isSingleItem } from '../../../..';
|
||||
import { ENRICH_MODES } from '../../../definitions/settings';
|
||||
import { SuggestionRawDefinition } from '../../types';
|
||||
import { TRIGGER_SUGGESTION_COMMAND, getSafeInsertText } from '../../factories';
|
||||
|
||||
|
@ -92,13 +92,25 @@ export const noPoliciesAvailableSuggestion: SuggestionRawDefinition = {
|
|||
},
|
||||
};
|
||||
|
||||
export const modeSuggestions: SuggestionRawDefinition[] = ENRICH_MODES.values.map(
|
||||
export const modeDescription = i18n.translate(
|
||||
'kbn-esql-validation-autocomplete.esql.definitions.ccqMode',
|
||||
{
|
||||
defaultMessage: 'Cross-cluster query mode',
|
||||
}
|
||||
);
|
||||
|
||||
export const modeSuggestions: SuggestionRawDefinition[] = ENRICH_MODES.map(
|
||||
({ name, description }) => ({
|
||||
label: `${ENRICH_MODES.prefix || ''}${name}`,
|
||||
text: `${ENRICH_MODES.prefix || ''}${name}:$0`,
|
||||
label: `_${name}`,
|
||||
text: `_${name}:$0`,
|
||||
asSnippet: true,
|
||||
kind: 'Reference',
|
||||
detail: `${ENRICH_MODES.description} - ${description}`,
|
||||
detail: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.ccqModeDoc', {
|
||||
defaultMessage: 'Cross-cluster query mode - ${description}',
|
||||
values: {
|
||||
description,
|
||||
},
|
||||
}),
|
||||
sortText: 'D',
|
||||
command: TRIGGER_SUGGESTION_COMMAND,
|
||||
})
|
||||
|
|
|
@ -15,8 +15,15 @@ import {
|
|||
type ESQLFunction,
|
||||
isFunctionExpression,
|
||||
isWhereExpression,
|
||||
ESQLCommandMode,
|
||||
} from '@kbn/esql-ast';
|
||||
import { isAssignment, isColumnItem, isFunctionItem } from '../shared/helpers';
|
||||
import {
|
||||
isAssignment,
|
||||
isColumnItem,
|
||||
isFunctionItem,
|
||||
isSingleItem,
|
||||
noCaseCompare,
|
||||
} from '../shared/helpers';
|
||||
import {
|
||||
appendSeparatorOption,
|
||||
asOption,
|
||||
|
@ -25,10 +32,9 @@ import {
|
|||
onOption,
|
||||
withOption,
|
||||
} from './options';
|
||||
import { ENRICH_MODES } from './settings';
|
||||
|
||||
import { type CommandDefinition } from './types';
|
||||
import { checkAggExistence, checkFunctionContent } from './commands_helpers';
|
||||
import { ENRICH_MODES, checkAggExistence, checkFunctionContent } from './commands_helpers';
|
||||
|
||||
import { suggest as suggestForDissect } from '../autocomplete/commands/dissect';
|
||||
import { suggest as suggestForDrop } from '../autocomplete/commands/drop';
|
||||
|
@ -47,6 +53,8 @@ import { suggest as suggestForSort } from '../autocomplete/commands/sort';
|
|||
import { suggest as suggestForStats } from '../autocomplete/commands/stats';
|
||||
import { suggest as suggestForWhere } from '../autocomplete/commands/where';
|
||||
|
||||
import { getMessageFromId } from '../validation/errors';
|
||||
|
||||
const statsValidator = (command: ESQLCommand) => {
|
||||
const messages: ESQLMessage[] = [];
|
||||
const commandName = command.name.toUpperCase();
|
||||
|
@ -147,7 +155,6 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
|
|||
},
|
||||
suggest: suggestForRow,
|
||||
options: [],
|
||||
modes: [],
|
||||
},
|
||||
{
|
||||
name: 'from',
|
||||
|
@ -157,7 +164,6 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
|
|||
}),
|
||||
examples: ['from logs', 'from logs-*', 'from logs_*, events-*'],
|
||||
options: [metadataOption],
|
||||
modes: [],
|
||||
signature: {
|
||||
multipleParams: true,
|
||||
params: [{ name: 'index', type: 'source', wildcards: true }],
|
||||
|
@ -171,7 +177,6 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
|
|||
}),
|
||||
examples: ['SHOW INFO'],
|
||||
options: [],
|
||||
modes: [],
|
||||
signature: {
|
||||
multipleParams: false,
|
||||
params: [{ name: 'functions', type: 'function' }],
|
||||
|
@ -200,7 +205,6 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
|
|||
'metrics src1, src2 agg1, agg2 by field1, field2',
|
||||
],
|
||||
options: [],
|
||||
modes: [],
|
||||
signature: {
|
||||
multipleParams: true,
|
||||
params: [
|
||||
|
@ -222,7 +226,6 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
|
|||
params: [{ name: 'expression', type: 'function', optional: true }],
|
||||
},
|
||||
options: [byOption],
|
||||
modes: [],
|
||||
validate: statsValidator,
|
||||
suggest: suggestForStats,
|
||||
},
|
||||
|
@ -242,7 +245,6 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
|
|||
params: [{ name: 'expression', type: 'function', optional: true }],
|
||||
},
|
||||
options: [byOption],
|
||||
modes: [],
|
||||
// Reusing the same validation logic as stats command
|
||||
validate: statsValidator,
|
||||
suggest: () => [],
|
||||
|
@ -265,7 +267,6 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
|
|||
params: [{ name: 'expression', type: 'any' }],
|
||||
},
|
||||
options: [],
|
||||
modes: [],
|
||||
suggest: suggestForEval,
|
||||
},
|
||||
{
|
||||
|
@ -279,7 +280,6 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
|
|||
params: [{ name: 'renameClause', type: 'column' }],
|
||||
},
|
||||
options: [asOption],
|
||||
modes: [],
|
||||
suggest: suggestForRename,
|
||||
},
|
||||
{
|
||||
|
@ -294,7 +294,6 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
|
|||
params: [{ name: 'size', type: 'integer', constantOnly: true }],
|
||||
},
|
||||
options: [],
|
||||
modes: [],
|
||||
suggest: suggestForLimit,
|
||||
},
|
||||
{
|
||||
|
@ -306,7 +305,6 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
|
|||
examples: ['… | keep a', '… | keep a,b'],
|
||||
suggest: suggestForKeep,
|
||||
options: [],
|
||||
modes: [],
|
||||
signature: {
|
||||
multipleParams: true,
|
||||
params: [{ name: 'column', type: 'column', wildcards: true }],
|
||||
|
@ -319,7 +317,6 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
|
|||
}),
|
||||
examples: ['… | drop a', '… | drop a,b'],
|
||||
options: [],
|
||||
modes: [],
|
||||
signature: {
|
||||
multipleParams: true,
|
||||
params: [{ name: 'column', type: 'column', wildcards: true }],
|
||||
|
@ -376,7 +373,6 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
|
|||
'… | sort a - abs(b)',
|
||||
],
|
||||
options: [],
|
||||
modes: [],
|
||||
signature: {
|
||||
multipleParams: true,
|
||||
params: [{ name: 'expression', type: 'any' }],
|
||||
|
@ -396,7 +392,6 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
|
|||
params: [{ name: 'expression', type: 'boolean' }],
|
||||
},
|
||||
options: [],
|
||||
modes: [],
|
||||
suggest: suggestForWhere,
|
||||
},
|
||||
{
|
||||
|
@ -407,7 +402,6 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
|
|||
}),
|
||||
examples: ['… | DISSECT a "%{b} %{c}" APPEND_SEPARATOR = ":"'],
|
||||
options: [appendSeparatorOption],
|
||||
modes: [],
|
||||
signature: {
|
||||
multipleParams: false,
|
||||
params: [
|
||||
|
@ -425,7 +419,6 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
|
|||
}),
|
||||
examples: ['… | GROK a "%{IP:b} %{NUMBER:c}"'],
|
||||
options: [],
|
||||
modes: [],
|
||||
signature: {
|
||||
multipleParams: false,
|
||||
params: [
|
||||
|
@ -442,7 +435,6 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
|
|||
}),
|
||||
examples: ['row a=[1,2,3] | mv_expand a'],
|
||||
options: [],
|
||||
modes: [],
|
||||
preview: true,
|
||||
signature: {
|
||||
multipleParams: false,
|
||||
|
@ -462,12 +454,37 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
|
|||
'… | enrich my-policy on pivotField with a = enrichFieldA, b = enrichFieldB',
|
||||
],
|
||||
options: [onOption, withOption],
|
||||
modes: [ENRICH_MODES],
|
||||
signature: {
|
||||
multipleParams: false,
|
||||
params: [{ name: 'policyName', type: 'source', innerTypes: ['policy'] }],
|
||||
},
|
||||
suggest: suggestForEnrich,
|
||||
validate: (command: ESQLCommand) => {
|
||||
const modeArg = command.args.find((arg) => isSingleItem(arg) && arg.type === 'mode') as
|
||||
| ESQLCommandMode
|
||||
| undefined;
|
||||
|
||||
if (!modeArg) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const acceptedValues = ENRICH_MODES.map(({ name }) => '_' + name);
|
||||
if (acceptedValues.some((value) => noCaseCompare(modeArg.text, value))) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
getMessageFromId({
|
||||
messageId: 'unsupportedMode',
|
||||
values: {
|
||||
command: 'ENRICH',
|
||||
value: modeArg.text,
|
||||
expected: acceptedValues.join(', '),
|
||||
},
|
||||
locations: modeArg.location,
|
||||
}),
|
||||
];
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'hidden_command',
|
||||
|
@ -475,7 +492,6 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
|
|||
hidden: true,
|
||||
examples: [],
|
||||
options: [],
|
||||
modes: [],
|
||||
signature: {
|
||||
params: [],
|
||||
multipleParams: false,
|
||||
|
@ -527,7 +543,6 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
|
|||
// '… | <LEFT | RIGHT | LOOKUP> JOIN index AS alias ON index.field = index2.field',
|
||||
// '… | <LEFT | RIGHT | LOOKUP> JOIN index AS alias ON index.field = index2.field, index.field2 = index2.field2',
|
||||
],
|
||||
modes: [],
|
||||
signature: {
|
||||
multipleParams: true,
|
||||
params: [{ name: 'index', type: 'source', wildcards: true }],
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
isFieldExpression,
|
||||
Walker,
|
||||
} from '@kbn/esql-ast';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
getFunctionDefinition,
|
||||
isFunctionItem,
|
||||
|
@ -80,3 +81,27 @@ export function checkAggExistence(arg: ESQLFunction): boolean {
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
export const ENRICH_MODES = [
|
||||
{
|
||||
name: 'any',
|
||||
description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.ccqAnyDoc', {
|
||||
defaultMessage: 'Enrich takes place on any cluster',
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'coordinator',
|
||||
description: i18n.translate(
|
||||
'kbn-esql-validation-autocomplete.esql.definitions.ccqCoordinatorDoc',
|
||||
{
|
||||
defaultMessage: 'Enrich takes place on the coordinating cluster receiving an ES|QL',
|
||||
}
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'remote',
|
||||
description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.ccqRemoteDoc', {
|
||||
defaultMessage: 'Enrich takes place on the cluster hosting the target index.',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
|
|
@ -1,45 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { CommandModeDefinition } from './types';
|
||||
|
||||
export const ENRICH_MODES: CommandModeDefinition = {
|
||||
name: 'ccq.mode',
|
||||
description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.ccqModeDoc', {
|
||||
defaultMessage: 'Cross-clusters query mode',
|
||||
}),
|
||||
prefix: '_',
|
||||
values: [
|
||||
{
|
||||
name: 'any',
|
||||
description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.ccqAnyDoc', {
|
||||
defaultMessage: 'Enrich takes place on any cluster',
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'coordinator',
|
||||
description: i18n.translate(
|
||||
'kbn-esql-validation-autocomplete.esql.definitions.ccqCoordinatorDoc',
|
||||
{
|
||||
defaultMessage: 'Enrich takes place on the coordinating cluster receiving an ES|QL',
|
||||
}
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'remote',
|
||||
description: i18n.translate(
|
||||
'kbn-esql-validation-autocomplete.esql.definitions.ccqRemoteDoc',
|
||||
{
|
||||
defaultMessage: 'Enrich takes place on the cluster hosting the target index.',
|
||||
}
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
|
@ -16,7 +16,7 @@ import type {
|
|||
} from '@kbn/esql-ast';
|
||||
import { ESQLControlVariable } from '@kbn/esql-types';
|
||||
import { GetColumnsByTypeFn, SuggestionRawDefinition } from '../autocomplete/types';
|
||||
import type { ESQLPolicy } from '../validation/types';
|
||||
import type { ESQLPolicy, ReferenceMaps } from '../validation/types';
|
||||
import { ESQLCallbacks, ESQLSourceResult } from '../shared/types';
|
||||
|
||||
/**
|
||||
|
@ -339,21 +339,17 @@ export interface CommandOptionsDefinition<CommandName extends string = string>
|
|||
) => ESQLMessage[];
|
||||
}
|
||||
|
||||
export interface CommandModeDefinition {
|
||||
name: string;
|
||||
description: string;
|
||||
values: Array<{ name: string; description: string }>;
|
||||
prefix?: string;
|
||||
}
|
||||
|
||||
export interface CommandDefinition<CommandName extends string>
|
||||
extends CommandBaseDefinition<CommandName> {
|
||||
examples: string[];
|
||||
validate?: (option: ESQLCommand) => ESQLMessage[];
|
||||
/**
|
||||
* This function is run when the command is being validated, but it does not
|
||||
* prevent the default behavior. If you need a full override, we are currently
|
||||
* doing those directly in the validateCommand function in the validation module.
|
||||
*/
|
||||
validate?: (command: ESQLCommand<CommandName>, references: ReferenceMaps) => ESQLMessage[];
|
||||
suggest: CommandSuggestFunction<CommandName>;
|
||||
/** @deprecated this property will disappear in the future */
|
||||
modes: CommandModeDefinition[];
|
||||
/** @deprecated this property will disappear in the future */
|
||||
options: CommandOptionsDefinition[];
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ import {
|
|||
type ESQLTimeInterval,
|
||||
} from '@kbn/esql-ast';
|
||||
import {
|
||||
ESQLCommandMode,
|
||||
ESQLIdentifier,
|
||||
ESQLInlineCast,
|
||||
ESQLParamLiteral,
|
||||
|
@ -66,10 +65,6 @@ export function isSingleItem(arg: ESQLAstItem): arg is ESQLSingleAstItem {
|
|||
return arg && !Array.isArray(arg);
|
||||
}
|
||||
|
||||
/** @deprecated — a "setting" is a concept we will be getting rid of soon */
|
||||
export function isSettingItem(arg: ESQLAstItem): arg is ESQLCommandMode {
|
||||
return isSingleItem(arg) && arg.type === 'mode';
|
||||
}
|
||||
export function isFunctionItem(arg: ESQLAstItem): arg is ESQLFunction {
|
||||
return isSingleItem(arg) && arg.type === 'function';
|
||||
}
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import {
|
||||
ESQLAstJoinCommand,
|
||||
ESQLMessage,
|
||||
isBinaryExpression,
|
||||
isIdentifier,
|
||||
isSource,
|
||||
} from '@kbn/esql-ast';
|
||||
import { ESQLIdentifier, ESQLProperNode, ESQLSource } from '@kbn/esql-ast/src/types';
|
||||
import { ReferenceMaps } from '../../types';
|
||||
import { errors } from '../../errors';
|
||||
|
||||
/**
|
||||
* Validates the JOIN command:
|
||||
*
|
||||
* <LEFT | RIGHT | LOOKUP> JOIN <target> ON <conditions>
|
||||
* <LEFT | RIGHT | LOOKUP> JOIN index [ = alias ] ON <condition> [, <condition> [, ...]]
|
||||
*/
|
||||
export const validate = (command: ESQLAstJoinCommand, references: ReferenceMaps): ESQLMessage[] => {
|
||||
const messages: ESQLMessage[] = [];
|
||||
const { commandType, args } = command;
|
||||
const { joinIndices } = references;
|
||||
|
||||
if (!['left', 'right', 'lookup'].includes(commandType)) {
|
||||
return [errors.unexpected(command.location, 'JOIN command type')];
|
||||
}
|
||||
|
||||
const target = args[0] as ESQLProperNode;
|
||||
let index: ESQLSource;
|
||||
let alias: ESQLIdentifier | undefined;
|
||||
|
||||
if (isBinaryExpression(target)) {
|
||||
if (target.name === 'as') {
|
||||
alias = target.args[1] as ESQLIdentifier;
|
||||
index = target.args[0] as ESQLSource;
|
||||
|
||||
if (!isSource(index) || !isIdentifier(alias)) {
|
||||
return [errors.unexpected(target.location)];
|
||||
}
|
||||
} else {
|
||||
return [errors.unexpected(target.location)];
|
||||
}
|
||||
} else if (isSource(target)) {
|
||||
index = target as ESQLSource;
|
||||
} else {
|
||||
return [errors.unexpected(target.location)];
|
||||
}
|
||||
|
||||
let isIndexFound = false;
|
||||
for (const { name, aliases } of joinIndices) {
|
||||
if (index.name === name) {
|
||||
isIndexFound = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (aliases) {
|
||||
for (const aliasName of aliases) {
|
||||
if (index.name === aliasName) {
|
||||
isIndexFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isIndexFound) {
|
||||
const error = errors.invalidJoinIndex(index);
|
||||
messages.push(error);
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
return messages;
|
||||
};
|
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { ESQLAstMetricsCommand, ESQLCommand, ESQLMessage, isIdentifier, walk } from '@kbn/esql-ast';
|
||||
import { ESQLAstField, ESQLAstItem, ESQLFunction } from '@kbn/esql-ast/src/types';
|
||||
import {
|
||||
isAggFunction,
|
||||
isFunctionOperatorParam,
|
||||
isMaybeAggFunction,
|
||||
} from '../../../shared/helpers';
|
||||
import { FunctionDefinitionTypes } from '../../../definitions/types';
|
||||
import { ReferenceMaps } from '../../types';
|
||||
import {
|
||||
getFunctionDefinition,
|
||||
isAssignment,
|
||||
isColumnItem,
|
||||
isFunctionItem,
|
||||
isLiteralItem,
|
||||
} from '../../../..';
|
||||
import { errors } from '../../errors';
|
||||
import { validateFunction } from '../../function_validation';
|
||||
import { validateColumnForCommand, validateSources } from '../../validation';
|
||||
|
||||
/**
|
||||
* Validates the METRICS source command:
|
||||
*
|
||||
* METRICS <sources> [ <aggregates> [ BY <grouping> ]]
|
||||
*/
|
||||
export const validate = (
|
||||
command: ESQLAstMetricsCommand,
|
||||
references: ReferenceMaps
|
||||
): ESQLMessage[] => {
|
||||
const messages: ESQLMessage[] = [];
|
||||
const { sources, aggregates, grouping } = command;
|
||||
|
||||
// METRICS <sources> ...
|
||||
messages.push(...validateSources(command, sources, references));
|
||||
|
||||
// ... <aggregates> ...
|
||||
if (aggregates && aggregates.length) {
|
||||
messages.push(...validateAggregates(command, aggregates, references));
|
||||
|
||||
// ... BY <grouping>
|
||||
if (grouping && grouping.length) {
|
||||
messages.push(...validateByGrouping(grouping, 'metrics', references, true));
|
||||
}
|
||||
}
|
||||
|
||||
return messages;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates aggregates fields: `... <aggregates> ...`.
|
||||
*/
|
||||
const validateAggregates = (
|
||||
command: ESQLCommand,
|
||||
aggregates: ESQLAstField[],
|
||||
references: ReferenceMaps
|
||||
) => {
|
||||
const messages: ESQLMessage[] = [];
|
||||
|
||||
// Should never happen.
|
||||
if (!aggregates.length) {
|
||||
messages.push(errors.unexpected(command.location));
|
||||
return messages;
|
||||
}
|
||||
|
||||
let hasMissingAggregationFunctionError = false;
|
||||
|
||||
for (const aggregate of aggregates) {
|
||||
if (isFunctionItem(aggregate)) {
|
||||
messages.push(
|
||||
...validateFunction({
|
||||
fn: aggregate,
|
||||
parentCommand: command.name,
|
||||
parentOption: undefined,
|
||||
references,
|
||||
})
|
||||
);
|
||||
|
||||
let hasAggregationFunction = false;
|
||||
|
||||
walk(aggregate, {
|
||||
visitFunction: (fn) => {
|
||||
const definition = getFunctionDefinition(fn.name);
|
||||
if (!definition) return;
|
||||
if (definition.type === FunctionDefinitionTypes.AGG) hasAggregationFunction = true;
|
||||
},
|
||||
});
|
||||
|
||||
if (!hasAggregationFunction) {
|
||||
hasMissingAggregationFunctionError = true;
|
||||
messages.push(errors.noAggFunction(command, aggregate));
|
||||
}
|
||||
} else if (isColumnItem(aggregate) || isIdentifier(aggregate)) {
|
||||
messages.push(errors.unknownAggFunction(aggregate));
|
||||
} else {
|
||||
// Should never happen.
|
||||
}
|
||||
}
|
||||
|
||||
if (hasMissingAggregationFunctionError) {
|
||||
return messages;
|
||||
}
|
||||
|
||||
for (const aggregate of aggregates) {
|
||||
if (isFunctionItem(aggregate)) {
|
||||
const fn = isAssignment(aggregate) ? aggregate.args[1] : aggregate;
|
||||
if (isFunctionItem(fn) && !isFunctionAggClosed(fn)) {
|
||||
messages.push(errors.expressionNotAggClosed(command, fn));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (messages.length) {
|
||||
return messages;
|
||||
}
|
||||
|
||||
for (const aggregate of aggregates) {
|
||||
if (isFunctionItem(aggregate)) {
|
||||
const aggInAggFunction = findNestedAggFunction(aggregate);
|
||||
if (aggInAggFunction) {
|
||||
messages.push(errors.aggInAggFunction(aggInAggFunction));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return messages;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates grouping fields of the BY clause: `... BY <grouping>`.
|
||||
*/
|
||||
const validateByGrouping = (
|
||||
fields: ESQLAstItem[],
|
||||
commandName: string,
|
||||
referenceMaps: ReferenceMaps,
|
||||
multipleParams: boolean
|
||||
): ESQLMessage[] => {
|
||||
const messages: ESQLMessage[] = [];
|
||||
for (const field of fields) {
|
||||
if (!Array.isArray(field)) {
|
||||
if (!multipleParams) {
|
||||
if (isColumnItem(field)) {
|
||||
messages.push(...validateColumnForCommand(field, commandName, referenceMaps));
|
||||
}
|
||||
} else {
|
||||
if (isColumnItem(field)) {
|
||||
messages.push(...validateColumnForCommand(field, commandName, referenceMaps));
|
||||
}
|
||||
if (isFunctionItem(field)) {
|
||||
messages.push(
|
||||
...validateFunction({
|
||||
fn: field,
|
||||
parentCommand: commandName,
|
||||
parentOption: 'by',
|
||||
references: referenceMaps,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return messages;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate that a function is an aggregate function or that all children
|
||||
* recursively terminate at either a literal or an aggregate function.
|
||||
*/
|
||||
const isFunctionAggClosed = (fn: ESQLFunction): boolean =>
|
||||
isMaybeAggFunction(fn) || areFunctionArgsAggClosed(fn);
|
||||
|
||||
const areFunctionArgsAggClosed = (fn: ESQLFunction): boolean =>
|
||||
fn.args.every((arg) => isLiteralItem(arg) || (isFunctionItem(arg) && isFunctionAggClosed(arg))) ||
|
||||
isFunctionOperatorParam(fn);
|
||||
|
||||
/**
|
||||
* Looks for first nested aggregate function in an aggregate function, recursively.
|
||||
*/
|
||||
const findNestedAggFunctionInAggFunction = (agg: ESQLFunction): ESQLFunction | undefined => {
|
||||
for (const arg of agg.args) {
|
||||
if (isFunctionItem(arg)) {
|
||||
return isMaybeAggFunction(arg) ? arg : findNestedAggFunctionInAggFunction(arg);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Looks for first nested aggregate function in another aggregate a function,
|
||||
* recursively.
|
||||
*
|
||||
* @param fn Function to check for nested aggregate functions.
|
||||
* @param parentIsAgg Whether the parent function of `fn` is an aggregate function.
|
||||
* @returns The first nested aggregate function in `fn`, or `undefined` if none is found.
|
||||
*/
|
||||
const findNestedAggFunction = (
|
||||
fn: ESQLFunction,
|
||||
parentIsAgg: boolean = false
|
||||
): ESQLFunction | undefined => {
|
||||
if (isMaybeAggFunction(fn)) {
|
||||
return parentIsAgg ? fn : findNestedAggFunctionInAggFunction(fn);
|
||||
}
|
||||
|
||||
for (const arg of fn.args) {
|
||||
if (isFunctionItem(arg)) {
|
||||
const nestedAgg = findNestedAggFunction(arg, parentIsAgg || isAggFunction(fn));
|
||||
if (nestedAgg) return nestedAgg;
|
||||
}
|
||||
}
|
||||
};
|
|
@ -294,21 +294,7 @@ function getMessageAndTypeFromId<K extends ErrorTypes>({
|
|||
),
|
||||
type: 'warning',
|
||||
};
|
||||
case 'unsupportedSetting':
|
||||
return {
|
||||
message: i18n.translate(
|
||||
'kbn-esql-validation-autocomplete.esql.validation.unsupportedSetting',
|
||||
{
|
||||
defaultMessage: 'Unsupported setting [{setting}], expected [{expected}]',
|
||||
values: {
|
||||
setting: out.setting,
|
||||
expected: out.expected,
|
||||
},
|
||||
}
|
||||
),
|
||||
type: 'error',
|
||||
};
|
||||
case 'unsupportedSettingCommandValue':
|
||||
case 'unsupportedMode':
|
||||
return {
|
||||
message: i18n.translate(
|
||||
'kbn-esql-validation-autocomplete.esql.validation.unsupportedSettingValue',
|
||||
|
|
|
@ -9294,7 +9294,7 @@
|
|||
{
|
||||
"query": "from a_index | enrich _:policy",
|
||||
"error": [
|
||||
"Unrecognized value [_] for ENRICH, mode needs to be one of [_ANY, _COORDINATOR, _REMOTE]"
|
||||
"Unrecognized value [_] for ENRICH, mode needs to be one of [_any, _coordinator, _remote]"
|
||||
],
|
||||
"warning": []
|
||||
},
|
||||
|
@ -9324,7 +9324,7 @@
|
|||
{
|
||||
"query": "from a_index | enrich any:policy",
|
||||
"error": [
|
||||
"Unrecognized value [any] for ENRICH, mode needs to be one of [_ANY, _COORDINATOR, _REMOTE]"
|
||||
"Unrecognized value [any] for ENRICH, mode needs to be one of [_any, _coordinator, _remote]"
|
||||
],
|
||||
"warning": []
|
||||
},
|
||||
|
@ -9451,7 +9451,7 @@
|
|||
{
|
||||
"query": "from a_index | enrich _unknown:policy",
|
||||
"error": [
|
||||
"Unrecognized value [_unknown] for ENRICH, mode needs to be one of [_ANY, _COORDINATOR, _REMOTE]"
|
||||
"Unrecognized value [_unknown] for ENRICH, mode needs to be one of [_any, _coordinator, _remote]"
|
||||
],
|
||||
"warning": []
|
||||
},
|
||||
|
|
|
@ -0,0 +1,670 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { ESQLAstItem, ESQLCommand, ESQLFunction, ESQLMessage, isIdentifier } from '@kbn/esql-ast';
|
||||
import { uniqBy } from 'lodash';
|
||||
import {
|
||||
isLiteralItem,
|
||||
isTimeIntervalItem,
|
||||
isFunctionItem,
|
||||
isSupportedFunction,
|
||||
getFunctionDefinition,
|
||||
isColumnItem,
|
||||
isAssignment,
|
||||
} from '../..';
|
||||
import { FunctionParameter, FunctionDefinitionTypes } from '../definitions/types';
|
||||
import {
|
||||
UNSUPPORTED_COMMANDS_BEFORE_MATCH,
|
||||
UNSUPPORTED_COMMANDS_BEFORE_QSTR,
|
||||
} from '../shared/constants';
|
||||
import { compareTypesWithLiterals } from '../shared/esql_types';
|
||||
import {
|
||||
isValidLiteralOption,
|
||||
checkFunctionArgMatchesDefinition,
|
||||
inKnownTimeInterval,
|
||||
isInlineCastItem,
|
||||
getQuotedColumnName,
|
||||
getColumnExists,
|
||||
getColumnForASTNode,
|
||||
isFunctionOperatorParam,
|
||||
getSignaturesWithMatchingArity,
|
||||
getParamAtPosition,
|
||||
extractSingularType,
|
||||
isArrayType,
|
||||
} from '../shared/helpers';
|
||||
import { getMessageFromId, errors } from './errors';
|
||||
import { getMaxMinNumberOfParams, collapseWrongArgumentTypeMessages } from './helpers';
|
||||
import { ReferenceMaps } from './types';
|
||||
|
||||
const NO_MESSAGE: ESQLMessage[] = [];
|
||||
|
||||
/**
|
||||
* Performs validation on a function
|
||||
*/
|
||||
export function validateFunction({
|
||||
fn,
|
||||
parentCommand,
|
||||
parentOption,
|
||||
references,
|
||||
forceConstantOnly = false,
|
||||
isNested,
|
||||
parentAst,
|
||||
currentCommandIndex,
|
||||
}: {
|
||||
fn: ESQLFunction;
|
||||
parentCommand: string;
|
||||
parentOption?: string;
|
||||
references: ReferenceMaps;
|
||||
forceConstantOnly?: boolean;
|
||||
isNested?: boolean;
|
||||
parentAst?: ESQLCommand[];
|
||||
currentCommandIndex?: number;
|
||||
}): ESQLMessage[] {
|
||||
const messages: ESQLMessage[] = [];
|
||||
|
||||
if (fn.incomplete) {
|
||||
return messages;
|
||||
}
|
||||
if (isFunctionOperatorParam(fn)) {
|
||||
return messages;
|
||||
}
|
||||
const fnDefinition = getFunctionDefinition(fn.name)!;
|
||||
|
||||
const isFnSupported = isSupportedFunction(fn.name, parentCommand, parentOption);
|
||||
|
||||
if (typeof textSearchFunctionsValidators[fn.name] === 'function') {
|
||||
const validator = textSearchFunctionsValidators[fn.name];
|
||||
messages.push(
|
||||
...validator({
|
||||
fn,
|
||||
parentCommand,
|
||||
parentOption,
|
||||
references,
|
||||
isNested,
|
||||
parentAst,
|
||||
currentCommandIndex,
|
||||
})
|
||||
);
|
||||
}
|
||||
if (!isFnSupported.supported) {
|
||||
if (isFnSupported.reason === 'unknownFunction') {
|
||||
messages.push(errors.unknownFunction(fn));
|
||||
}
|
||||
// for nested functions skip this check and make the nested check fail later on
|
||||
if (isFnSupported.reason === 'unsupportedFunction' && !isNested) {
|
||||
messages.push(
|
||||
parentOption
|
||||
? getMessageFromId({
|
||||
messageId: 'unsupportedFunctionForCommandOption',
|
||||
values: {
|
||||
name: fn.name,
|
||||
command: parentCommand.toUpperCase(),
|
||||
option: parentOption.toUpperCase(),
|
||||
},
|
||||
locations: fn.location,
|
||||
})
|
||||
: getMessageFromId({
|
||||
messageId: 'unsupportedFunctionForCommand',
|
||||
values: { name: fn.name, command: parentCommand.toUpperCase() },
|
||||
locations: fn.location,
|
||||
})
|
||||
);
|
||||
}
|
||||
if (messages.length) {
|
||||
return messages;
|
||||
}
|
||||
}
|
||||
const matchingSignatures = getSignaturesWithMatchingArity(fnDefinition, fn);
|
||||
if (!matchingSignatures.length) {
|
||||
const { max, min } = getMaxMinNumberOfParams(fnDefinition);
|
||||
if (max === min) {
|
||||
messages.push(
|
||||
getMessageFromId({
|
||||
messageId: 'wrongArgumentNumber',
|
||||
values: {
|
||||
fn: fn.name,
|
||||
numArgs: max,
|
||||
passedArgs: fn.args.length,
|
||||
},
|
||||
locations: fn.location,
|
||||
})
|
||||
);
|
||||
} else if (fn.args.length > max) {
|
||||
messages.push(
|
||||
getMessageFromId({
|
||||
messageId: 'wrongArgumentNumberTooMany',
|
||||
values: {
|
||||
fn: fn.name,
|
||||
numArgs: max,
|
||||
passedArgs: fn.args.length,
|
||||
extraArgs: fn.args.length - max,
|
||||
},
|
||||
locations: fn.location,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
messages.push(
|
||||
getMessageFromId({
|
||||
messageId: 'wrongArgumentNumberTooFew',
|
||||
values: {
|
||||
fn: fn.name,
|
||||
numArgs: min,
|
||||
passedArgs: fn.args.length,
|
||||
missingArgs: min - fn.args.length,
|
||||
},
|
||||
locations: fn.location,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
// now perform the same check on all functions args
|
||||
for (let i = 0; i < fn.args.length; i++) {
|
||||
const arg = fn.args[i];
|
||||
|
||||
const allMatchingArgDefinitionsAreConstantOnly = matchingSignatures.every((signature) => {
|
||||
return signature.params[i]?.constantOnly;
|
||||
});
|
||||
const wrappedArray = Array.isArray(arg) ? arg : [arg];
|
||||
for (const _subArg of wrappedArray) {
|
||||
/**
|
||||
* we need to remove the inline casts
|
||||
* to see if there's a function under there
|
||||
*
|
||||
* e.g. for ABS(CEIL(numberField)::int), we need to validate CEIL(numberField)
|
||||
*/
|
||||
const subArg = removeInlineCasts(_subArg);
|
||||
|
||||
if (isFunctionItem(subArg)) {
|
||||
const messagesFromArg = validateFunction({
|
||||
fn: subArg,
|
||||
parentCommand,
|
||||
parentOption,
|
||||
references,
|
||||
/**
|
||||
* The constantOnly constraint needs to be enforced for arguments that
|
||||
* are functions as well, regardless of whether the definition for the
|
||||
* sub function's arguments includes the constantOnly flag.
|
||||
*
|
||||
* Example:
|
||||
* bucket(@timestamp, abs(bytes), "", "")
|
||||
*
|
||||
* In the above example, the abs function is not defined with the
|
||||
* constantOnly flag, but the second parameter in bucket _is_ defined
|
||||
* with the constantOnly flag.
|
||||
*
|
||||
* Because of this, the abs function's arguments inherit the constraint
|
||||
* and each should be validated as if each were constantOnly.
|
||||
*/
|
||||
forceConstantOnly: allMatchingArgDefinitionsAreConstantOnly || forceConstantOnly,
|
||||
// use the nesting flag for now just for stats and metrics
|
||||
// TODO: revisit this part later on to make it more generic
|
||||
isNested: ['stats', 'inlinestats', 'metrics'].includes(parentCommand)
|
||||
? isNested || !isAssignment(fn)
|
||||
: false,
|
||||
parentAst,
|
||||
});
|
||||
|
||||
if (messagesFromArg.some(({ code }) => code === 'expectedConstant')) {
|
||||
const consolidatedMessage = getMessageFromId({
|
||||
messageId: 'expectedConstant',
|
||||
values: {
|
||||
fn: fn.name,
|
||||
given: subArg.text,
|
||||
},
|
||||
locations: subArg.location,
|
||||
});
|
||||
|
||||
messages.push(
|
||||
consolidatedMessage,
|
||||
...messagesFromArg.filter(({ code }) => code !== 'expectedConstant')
|
||||
);
|
||||
} else {
|
||||
messages.push(...messagesFromArg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// check if the definition has some specific validation to apply:
|
||||
if (fnDefinition.validate) {
|
||||
const payloads = fnDefinition.validate(fn);
|
||||
if (payloads.length) {
|
||||
messages.push(...payloads);
|
||||
}
|
||||
}
|
||||
// at this point we're sure that at least one signature is matching
|
||||
const failingSignatures: ESQLMessage[][] = [];
|
||||
let relevantFuncSignatures = matchingSignatures;
|
||||
const enrichedArgs = fn.args;
|
||||
|
||||
if (fn.name === 'in' || fn.name === 'not_in') {
|
||||
for (let argIndex = 1; argIndex < fn.args.length; argIndex++) {
|
||||
relevantFuncSignatures = fnDefinition.signatures.filter(
|
||||
(s) =>
|
||||
s.params?.length >= argIndex &&
|
||||
s.params.slice(0, argIndex).every(({ type: dataType }, idx) => {
|
||||
const arg = enrichedArgs[idx];
|
||||
|
||||
if (isLiteralItem(arg)) {
|
||||
return (
|
||||
dataType === arg.literalType || compareTypesWithLiterals(dataType, arg.literalType)
|
||||
);
|
||||
}
|
||||
return false; // Non-literal arguments don't match
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (const signature of relevantFuncSignatures) {
|
||||
const failingSignature: ESQLMessage[] = [];
|
||||
fn.args.forEach((outerArg, index) => {
|
||||
const argDef = getParamAtPosition(signature, index);
|
||||
if ((!outerArg && argDef?.optional) || !argDef) {
|
||||
// that's ok, just skip it
|
||||
// the else case is already catched with the argument counts check
|
||||
// few lines above
|
||||
return;
|
||||
}
|
||||
|
||||
// check every element of the argument (may be an array of elements, or may be a single element)
|
||||
const hasMultipleElements = Array.isArray(outerArg);
|
||||
const argElements = hasMultipleElements ? outerArg : [outerArg];
|
||||
const singularType = extractSingularType(argDef.type);
|
||||
const messagesFromAllArgElements = argElements.flatMap((arg) => {
|
||||
return [
|
||||
validateFunctionLiteralArg,
|
||||
validateNestedFunctionArg,
|
||||
validateFunctionColumnArg,
|
||||
validateInlineCastArg,
|
||||
].flatMap((validateFn) => {
|
||||
return validateFn(
|
||||
fn,
|
||||
arg,
|
||||
{
|
||||
...argDef,
|
||||
type: singularType,
|
||||
constantOnly: forceConstantOnly || argDef.constantOnly,
|
||||
},
|
||||
references,
|
||||
parentCommand
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const shouldCollapseMessages = isArrayType(argDef.type as string) && hasMultipleElements;
|
||||
failingSignature.push(
|
||||
...(shouldCollapseMessages
|
||||
? collapseWrongArgumentTypeMessages(
|
||||
messagesFromAllArgElements,
|
||||
outerArg,
|
||||
fn.name,
|
||||
argDef.type as string,
|
||||
parentCommand,
|
||||
references
|
||||
)
|
||||
: messagesFromAllArgElements)
|
||||
);
|
||||
});
|
||||
if (failingSignature.length) {
|
||||
failingSignatures.push(failingSignature);
|
||||
}
|
||||
}
|
||||
|
||||
if (failingSignatures.length && failingSignatures.length === relevantFuncSignatures.length) {
|
||||
const failingSignatureOrderedByErrorCount = failingSignatures
|
||||
.map((arr, index) => ({ index, count: arr.length }))
|
||||
.sort((a, b) => a.count - b.count);
|
||||
const indexForShortestFailingsignature = failingSignatureOrderedByErrorCount[0].index;
|
||||
messages.push(...failingSignatures[indexForShortestFailingsignature]);
|
||||
}
|
||||
// This is due to a special case in enrich where an implicit assignment is possible
|
||||
// so the AST needs to store an explicit "columnX = columnX" which duplicates the message
|
||||
return uniqBy(messages, ({ location }) => `${location.min}-${location.max}`);
|
||||
}
|
||||
|
||||
// #region Arg validation
|
||||
|
||||
function validateFunctionLiteralArg(
|
||||
astFunction: ESQLFunction,
|
||||
actualArg: ESQLAstItem,
|
||||
argDef: FunctionParameter,
|
||||
references: ReferenceMaps,
|
||||
parentCommand: string
|
||||
) {
|
||||
const messages: ESQLMessage[] = [];
|
||||
if (isLiteralItem(actualArg)) {
|
||||
if (
|
||||
actualArg.literalType === 'keyword' &&
|
||||
argDef.acceptedValues &&
|
||||
isValidLiteralOption(actualArg, argDef)
|
||||
) {
|
||||
messages.push(
|
||||
getMessageFromId({
|
||||
messageId: 'unsupportedLiteralOption',
|
||||
values: {
|
||||
name: astFunction.name,
|
||||
value: actualArg.value,
|
||||
supportedOptions: argDef.acceptedValues?.map((option) => `"${option}"`).join(', '),
|
||||
},
|
||||
locations: actualArg.location,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (!checkFunctionArgMatchesDefinition(actualArg, argDef, references, parentCommand)) {
|
||||
messages.push(
|
||||
getMessageFromId({
|
||||
messageId: 'wrongArgumentType',
|
||||
values: {
|
||||
name: astFunction.name,
|
||||
argType: argDef.type as string,
|
||||
value: actualArg.text,
|
||||
givenType: actualArg.literalType,
|
||||
},
|
||||
locations: actualArg.location,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
if (isTimeIntervalItem(actualArg)) {
|
||||
// check first if it's a valid interval string
|
||||
if (!inKnownTimeInterval(actualArg.unit)) {
|
||||
messages.push(
|
||||
getMessageFromId({
|
||||
messageId: 'unknownInterval',
|
||||
values: {
|
||||
value: actualArg.unit,
|
||||
},
|
||||
locations: actualArg.location,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
if (!checkFunctionArgMatchesDefinition(actualArg, argDef, references, parentCommand)) {
|
||||
messages.push(
|
||||
getMessageFromId({
|
||||
messageId: 'wrongArgumentType',
|
||||
values: {
|
||||
name: astFunction.name,
|
||||
argType: argDef.type as string,
|
||||
value: actualArg.name,
|
||||
givenType: 'duration',
|
||||
},
|
||||
locations: actualArg.location,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
|
||||
function validateInlineCastArg(
|
||||
astFunction: ESQLFunction,
|
||||
arg: ESQLAstItem,
|
||||
parameterDefinition: FunctionParameter,
|
||||
references: ReferenceMaps,
|
||||
parentCommand: string
|
||||
) {
|
||||
if (!isInlineCastItem(arg)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!checkFunctionArgMatchesDefinition(arg, parameterDefinition, references, parentCommand)) {
|
||||
return [
|
||||
getMessageFromId({
|
||||
messageId: 'wrongArgumentType',
|
||||
values: {
|
||||
name: astFunction.name,
|
||||
argType: parameterDefinition.type as string,
|
||||
value: arg.text,
|
||||
givenType: arg.castType,
|
||||
},
|
||||
locations: arg.location,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
function validateNestedFunctionArg(
|
||||
astFunction: ESQLFunction,
|
||||
actualArg: ESQLAstItem,
|
||||
parameterDefinition: FunctionParameter,
|
||||
references: ReferenceMaps,
|
||||
parentCommand: string
|
||||
) {
|
||||
const messages: ESQLMessage[] = [];
|
||||
if (
|
||||
isFunctionItem(actualArg) &&
|
||||
// no need to check the reason here, it is checked already above
|
||||
isSupportedFunction(actualArg.name, parentCommand).supported
|
||||
) {
|
||||
// The isSupported check ensure the definition exists
|
||||
const argFn = getFunctionDefinition(actualArg.name)!;
|
||||
const fnDef = getFunctionDefinition(astFunction.name)!;
|
||||
// no nestying criteria should be enforced only for same type function
|
||||
if (fnDef.type === FunctionDefinitionTypes.AGG && argFn.type === FunctionDefinitionTypes.AGG) {
|
||||
messages.push(
|
||||
getMessageFromId({
|
||||
messageId: 'noNestedArgumentSupport',
|
||||
values: { name: actualArg.text, argType: argFn.signatures[0].returnType as string },
|
||||
locations: actualArg.location,
|
||||
})
|
||||
);
|
||||
}
|
||||
if (
|
||||
!checkFunctionArgMatchesDefinition(actualArg, parameterDefinition, references, parentCommand)
|
||||
) {
|
||||
messages.push(
|
||||
getMessageFromId({
|
||||
messageId: 'wrongArgumentType',
|
||||
values: {
|
||||
name: astFunction.name,
|
||||
argType: parameterDefinition.type as string,
|
||||
value: actualArg.text,
|
||||
givenType: argFn.signatures[0].returnType as string,
|
||||
},
|
||||
locations: actualArg.location,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
|
||||
function validateFunctionColumnArg(
|
||||
astFunction: ESQLFunction,
|
||||
actualArg: ESQLAstItem,
|
||||
parameterDefinition: FunctionParameter,
|
||||
references: ReferenceMaps,
|
||||
parentCommand: string
|
||||
) {
|
||||
const messages: ESQLMessage[] = [];
|
||||
if (!(isColumnItem(actualArg) || isIdentifier(actualArg))) {
|
||||
return messages;
|
||||
}
|
||||
|
||||
const columnName = getQuotedColumnName(actualArg);
|
||||
const columnExists = getColumnExists(actualArg, references);
|
||||
|
||||
if (parameterDefinition.constantOnly) {
|
||||
messages.push(
|
||||
getMessageFromId({
|
||||
messageId: 'expectedConstant',
|
||||
values: {
|
||||
fn: astFunction.name,
|
||||
given: columnName,
|
||||
},
|
||||
locations: actualArg.location,
|
||||
})
|
||||
);
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
if (!columnExists) {
|
||||
messages.push(
|
||||
getMessageFromId({
|
||||
messageId: 'unknownColumn',
|
||||
values: {
|
||||
name: actualArg.name,
|
||||
},
|
||||
locations: actualArg.location,
|
||||
})
|
||||
);
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
if (actualArg.name === '*') {
|
||||
// if function does not support wildcards return a specific error
|
||||
if (!('supportsWildcard' in parameterDefinition) || !parameterDefinition.supportsWildcard) {
|
||||
messages.push(
|
||||
getMessageFromId({
|
||||
messageId: 'noWildcardSupportAsArg',
|
||||
values: {
|
||||
name: astFunction.name,
|
||||
},
|
||||
locations: actualArg.location,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
if (
|
||||
!checkFunctionArgMatchesDefinition(actualArg, parameterDefinition, references, parentCommand)
|
||||
) {
|
||||
const columnHit = getColumnForASTNode(actualArg, references);
|
||||
messages.push(
|
||||
getMessageFromId({
|
||||
messageId: 'wrongArgumentType',
|
||||
values: {
|
||||
name: astFunction.name,
|
||||
argType: parameterDefinition.type as string,
|
||||
value: actualArg.name,
|
||||
givenType: columnHit!.type,
|
||||
},
|
||||
locations: actualArg.location,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
function removeInlineCasts(arg: ESQLAstItem): ESQLAstItem {
|
||||
if (isInlineCastItem(arg)) {
|
||||
return removeInlineCasts(arg.value);
|
||||
}
|
||||
return arg;
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Specific functions
|
||||
|
||||
function validateIfHasUnsupportedCommandPrior(
|
||||
fn: ESQLFunction,
|
||||
parentAst: ESQLCommand[] = [],
|
||||
unsupportedCommands: Set<string>,
|
||||
currentCommandIndex?: number
|
||||
) {
|
||||
if (currentCommandIndex === undefined) {
|
||||
return NO_MESSAGE;
|
||||
}
|
||||
const unsupportedCommandsPrior = parentAst.filter(
|
||||
(cmd, idx) => idx <= currentCommandIndex && unsupportedCommands.has(cmd.name)
|
||||
);
|
||||
|
||||
if (unsupportedCommandsPrior.length > 0) {
|
||||
return [
|
||||
getMessageFromId({
|
||||
messageId: 'fnUnsupportedAfterCommand',
|
||||
values: {
|
||||
function: fn.name.toUpperCase(),
|
||||
command: unsupportedCommandsPrior[0].name.toUpperCase(),
|
||||
},
|
||||
locations: fn.location,
|
||||
}),
|
||||
];
|
||||
}
|
||||
return NO_MESSAGE;
|
||||
}
|
||||
|
||||
const validateMatchFunction: FunctionValidator = ({
|
||||
fn,
|
||||
parentCommand,
|
||||
parentOption,
|
||||
references,
|
||||
forceConstantOnly = false,
|
||||
isNested,
|
||||
parentAst,
|
||||
currentCommandIndex,
|
||||
}) => {
|
||||
if (fn.name === 'match') {
|
||||
if (parentCommand !== 'where') {
|
||||
return [
|
||||
getMessageFromId({
|
||||
messageId: 'onlyWhereCommandSupported',
|
||||
values: { fn: fn.name },
|
||||
locations: fn.location,
|
||||
}),
|
||||
];
|
||||
}
|
||||
return validateIfHasUnsupportedCommandPrior(
|
||||
fn,
|
||||
parentAst,
|
||||
UNSUPPORTED_COMMANDS_BEFORE_MATCH,
|
||||
currentCommandIndex
|
||||
);
|
||||
}
|
||||
return NO_MESSAGE;
|
||||
};
|
||||
|
||||
type FunctionValidator = (args: {
|
||||
fn: ESQLFunction;
|
||||
parentCommand: string;
|
||||
parentOption?: string;
|
||||
references: ReferenceMaps;
|
||||
forceConstantOnly?: boolean;
|
||||
isNested?: boolean;
|
||||
parentAst?: ESQLCommand[];
|
||||
currentCommandIndex?: number;
|
||||
}) => ESQLMessage[];
|
||||
|
||||
const validateQSTRFunction: FunctionValidator = ({
|
||||
fn,
|
||||
parentCommand,
|
||||
parentOption,
|
||||
references,
|
||||
forceConstantOnly = false,
|
||||
isNested,
|
||||
parentAst,
|
||||
currentCommandIndex,
|
||||
}) => {
|
||||
if (fn.name === 'qstr') {
|
||||
return validateIfHasUnsupportedCommandPrior(
|
||||
fn,
|
||||
parentAst,
|
||||
UNSUPPORTED_COMMANDS_BEFORE_QSTR,
|
||||
currentCommandIndex
|
||||
);
|
||||
}
|
||||
return NO_MESSAGE;
|
||||
};
|
||||
|
||||
const textSearchFunctionsValidators: Record<string, FunctionValidator> = {
|
||||
match: validateMatchFunction,
|
||||
qstr: validateQSTRFunction,
|
||||
};
|
||||
|
||||
// #endregion
|
|
@ -159,11 +159,7 @@ export interface ValidationErrors {
|
|||
message: string;
|
||||
type: { field: string };
|
||||
};
|
||||
unsupportedSetting: {
|
||||
message: string;
|
||||
type: { setting: string; expected: string };
|
||||
};
|
||||
unsupportedSettingCommandValue: {
|
||||
unsupportedMode: {
|
||||
message: string;
|
||||
type: { command: string; value: string; expected: string };
|
||||
};
|
||||
|
|
|
@ -1349,7 +1349,7 @@ describe('validation logic', () => {
|
|||
'Unknown policy [_]',
|
||||
]);
|
||||
testErrorsAndWarnings(`from a_index | enrich _:policy`, [
|
||||
'Unrecognized value [_] for ENRICH, mode needs to be one of [_ANY, _COORDINATOR, _REMOTE]',
|
||||
'Unrecognized value [_] for ENRICH, mode needs to be one of [_any, _coordinator, _remote]',
|
||||
]);
|
||||
testErrorsAndWarnings(`from a_index | enrich :policy`, [
|
||||
"SyntaxError: token recognition error at: ':'",
|
||||
|
@ -1363,7 +1363,7 @@ describe('validation logic', () => {
|
|||
'Unknown policy [_any]',
|
||||
]);
|
||||
testErrorsAndWarnings(`from a_index | enrich any:policy`, [
|
||||
'Unrecognized value [any] for ENRICH, mode needs to be one of [_ANY, _COORDINATOR, _REMOTE]',
|
||||
'Unrecognized value [any] for ENRICH, mode needs to be one of [_any, _coordinator, _remote]',
|
||||
]);
|
||||
testErrorsAndWarnings(`from a_index | enrich policy `, []);
|
||||
testErrorsAndWarnings('from a_index | enrich `this``is fine`', [
|
||||
|
@ -1391,7 +1391,7 @@ describe('validation logic', () => {
|
|||
}
|
||||
|
||||
testErrorsAndWarnings(`from a_index | enrich _unknown:policy`, [
|
||||
'Unrecognized value [_unknown] for ENRICH, mode needs to be one of [_ANY, _COORDINATOR, _REMOTE]',
|
||||
'Unrecognized value [_unknown] for ENRICH, mode needs to be one of [_any, _coordinator, _remote]',
|
||||
]);
|
||||
testErrorsAndWarnings(`from a_index |enrich missing-policy `, [
|
||||
'Unknown policy [missing-policy]',
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -11,11 +11,12 @@ import { monaco } from '../../../monaco_imports';
|
|||
import { getHoverItem } from './hover';
|
||||
import { getAstAndSyntaxErrors } from '@kbn/esql-ast';
|
||||
import {
|
||||
ENRICH_MODES,
|
||||
ESQLRealField,
|
||||
getFunctionDefinition,
|
||||
getFunctionSignatures,
|
||||
} from '@kbn/esql-validation-autocomplete';
|
||||
import { modeDescription } from '@kbn/esql-validation-autocomplete/src/autocomplete/commands/enrich/util';
|
||||
import { ENRICH_MODES } from '@kbn/esql-validation-autocomplete/src/definitions/commands_helpers';
|
||||
import { FieldType } from '@kbn/esql-validation-autocomplete/src/definitions/types';
|
||||
|
||||
const types: FieldType[] = ['keyword', 'double', 'date', 'boolean', 'ip'];
|
||||
|
@ -187,12 +188,11 @@ describe('hover', () => {
|
|||
testSuggestions(`from a | enrich policy on b `, 'non-policy', createPolicyContent);
|
||||
|
||||
describe('ccq mode', () => {
|
||||
for (const mode of ENRICH_MODES.values) {
|
||||
testSuggestions(
|
||||
`from a | enrich ${ENRICH_MODES.prefix || ''}${mode.name}:policy`,
|
||||
`${ENRICH_MODES.prefix || ''}${mode.name}`,
|
||||
() => [ENRICH_MODES.description, `**${mode.name}**: ${mode.description}`]
|
||||
);
|
||||
for (const mode of ENRICH_MODES) {
|
||||
testSuggestions(`from a | enrich _${mode.name}:policy`, `_${mode.name}`, () => [
|
||||
modeDescription,
|
||||
`**${mode.name}**: ${mode.description}`,
|
||||
]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,8 +14,6 @@ import {
|
|||
getFunctionDefinition,
|
||||
getFunctionSignatures,
|
||||
isSourceItem,
|
||||
isSettingItem,
|
||||
getCommandDefinition,
|
||||
type ESQLCallbacks,
|
||||
getPolicyHelper,
|
||||
collectVariables,
|
||||
|
@ -34,6 +32,8 @@ import {
|
|||
TIME_SYSTEM_PARAMS,
|
||||
} from '@kbn/esql-validation-autocomplete/src/autocomplete/factories';
|
||||
import { isESQLFunction, isESQLNamedParamLiteral } from '@kbn/esql-ast/src/types';
|
||||
import { ENRICH_MODES } from '@kbn/esql-validation-autocomplete/src/definitions/commands_helpers';
|
||||
import { modeDescription } from '@kbn/esql-validation-autocomplete/src/autocomplete/commands/enrich/util';
|
||||
import { monacoPositionToOffset } from '../shared/utils';
|
||||
import { monaco } from '../../../monaco_imports';
|
||||
import { getVariablesHoverContent } from './helpers';
|
||||
|
@ -217,22 +217,16 @@ export async function getHoverItem(
|
|||
);
|
||||
}
|
||||
}
|
||||
if (isSettingItem(astContext.node)) {
|
||||
const commandDef = getCommandDefinition(astContext.command.name);
|
||||
const settingDef = commandDef?.modes.find(({ values }) =>
|
||||
values.some(({ name }) => name === astContext.node!.name)
|
||||
if (astContext.node.type === 'mode') {
|
||||
const mode = ENRICH_MODES.find(({ name }) => name === astContext.node!.name)!;
|
||||
hoverContent.contents.push(
|
||||
...[
|
||||
{ value: modeDescription },
|
||||
{
|
||||
value: `**${mode.name}**: ${mode.description}`,
|
||||
},
|
||||
]
|
||||
);
|
||||
if (settingDef) {
|
||||
const mode = settingDef.values.find(({ name }) => name === astContext.node!.name)!;
|
||||
hoverContent.contents.push(
|
||||
...[
|
||||
{ value: settingDef.description },
|
||||
{
|
||||
value: `**${mode.name}**: ${mode.description}`,
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5653,7 +5653,6 @@
|
|||
"kbn-esql-validation-autocomplete.esql.definitions.cbrt": "Renvoie la racine cubique d'un nombre. La valeur de renvoi est toujours un double, quelle que soit la valeur numérique de l'entrée. La racine cubique de l’infini est nulle.",
|
||||
"kbn-esql-validation-autocomplete.esql.definitions.ccqAnyDoc": "L'enrichissement a lieu sur n'importe quel cluster",
|
||||
"kbn-esql-validation-autocomplete.esql.definitions.ccqCoordinatorDoc": "L'enrichissement a lieu sur le cluster de coordination qui reçoit une requête ES|QL",
|
||||
"kbn-esql-validation-autocomplete.esql.definitions.ccqModeDoc": "Mode de requête inter-clusters",
|
||||
"kbn-esql-validation-autocomplete.esql.definitions.ccqRemoteDoc": "L'enrichissement a lieu sur le cluster qui héberge l'index cible.",
|
||||
"kbn-esql-validation-autocomplete.esql.definitions.ceil": "Arrondir un nombre à l'entier supérieur.",
|
||||
"kbn-esql-validation-autocomplete.esql.definitions.cidr_match": "Renvoie true si l'IP fournie est contenue dans l'un des blocs CIDR fournis.",
|
||||
|
@ -5863,7 +5862,6 @@
|
|||
"kbn-esql-validation-autocomplete.esql.validation.unsupportedFunctionForCommand": "{command} n'est pas compatible avec la fonction {name}",
|
||||
"kbn-esql-validation-autocomplete.esql.validation.unsupportedFunctionforCommandOption": "{command} {option} n'est pas compatible avec la fonction {name}",
|
||||
"kbn-esql-validation-autocomplete.esql.validation.unsupportedLiteralOption": "Option non valide [{value}] pour {name}. Options prises en charge : [{supportedOptions}].",
|
||||
"kbn-esql-validation-autocomplete.esql.validation.unsupportedSetting": "Paramètre non pris en charge [{setting}], [{expected}] attendu",
|
||||
"kbn-esql-validation-autocomplete.esql.validation.unsupportedSettingValue": "Valeur [{value}] non reconnue pour {command}, le mode doit être l'un de [{expected}]",
|
||||
"kbn-esql-validation-autocomplete.esql.validation.unsupportedTypeForCommand": "{command} n'est pas compatible avec [{type}] dans l'expression [{value}]",
|
||||
"kbn-esql-validation-autocomplete.esql.validation.wildcardNotSupportedForCommand": "L'utilisation de caractères génériques (*) dans {command} n'est pas autorisée [{value}]",
|
||||
|
|
|
@ -5648,7 +5648,6 @@
|
|||
"kbn-esql-validation-autocomplete.esql.definitions.cbrt": "数値の立方根を返します。入力は任意の数値で、戻り値は常にdoubleです。無限大の立方根はnullです。",
|
||||
"kbn-esql-validation-autocomplete.esql.definitions.ccqAnyDoc": "エンリッチは任意のクラスターで発生します",
|
||||
"kbn-esql-validation-autocomplete.esql.definitions.ccqCoordinatorDoc": "エンリッチは、ES|QLを受信する調整クラスターで実行されます",
|
||||
"kbn-esql-validation-autocomplete.esql.definitions.ccqModeDoc": "クラスター横断クエリーモード",
|
||||
"kbn-esql-validation-autocomplete.esql.definitions.ccqRemoteDoc": "エンリッチはターゲットインデックスをホスティングするクラスターで発生します。",
|
||||
"kbn-esql-validation-autocomplete.esql.definitions.ceil": "最も近い整数に数値を切り上げます。",
|
||||
"kbn-esql-validation-autocomplete.esql.definitions.cidr_match": "指定されたIPが指定されたCIDRブロックのいずれかに含まれていればtrueを返します。",
|
||||
|
@ -5857,7 +5856,6 @@
|
|||
"kbn-esql-validation-autocomplete.esql.validation.unsupportedColumnTypeForCommand": "{command}は{type} {typeCount, plural, other {型}}の値のみをサポートしていますが、[{givenType}]型の[{column}]が見つかりました",
|
||||
"kbn-esql-validation-autocomplete.esql.validation.unsupportedFieldType": "フィールド[{field}]を取得できません。サポートされていないか、インデックス化されていません。NULLが返されます",
|
||||
"kbn-esql-validation-autocomplete.esql.validation.unsupportedLiteralOption": "{name}の無効なオプション [{value}]です。サポートされているオプション:[{supportedOptions}]。",
|
||||
"kbn-esql-validation-autocomplete.esql.validation.unsupportedSetting": "サポートされていない設定[{setting}]です。[{expected}]でなければなりません",
|
||||
"kbn-esql-validation-autocomplete.esql.validation.unsupportedSettingValue": "{command}の認識されていない値[{value}]です。モードは[{expected}]のいずれかでなければなりません",
|
||||
"kbn-esql-validation-autocomplete.esql.validation.unsupportedTypeForCommand": "{command}は式[{value}]で[{type}]をサポートしていません",
|
||||
"kbn-esql-validation-autocomplete.esql.validation.wildcardNotSupportedForCommand": "{command}でのワイルドカード(*)の使用は許可されていません[{value}]",
|
||||
|
|
|
@ -5658,7 +5658,6 @@
|
|||
"kbn-esql-validation-autocomplete.esql.definitions.cbrt": "返回数字的立方根。输入可以为任何数字值,返回值始终为双精度值。无穷大的立方根为 null。",
|
||||
"kbn-esql-validation-autocomplete.esql.definitions.ccqAnyDoc": "扩充在任何集群上发生",
|
||||
"kbn-esql-validation-autocomplete.esql.definitions.ccqCoordinatorDoc": "扩充在接收 ES|QL 的协调集群上发生",
|
||||
"kbn-esql-validation-autocomplete.esql.definitions.ccqModeDoc": "跨集群查询模式",
|
||||
"kbn-esql-validation-autocomplete.esql.definitions.ccqRemoteDoc": "扩充在托管目标索引的集群上发生。",
|
||||
"kbn-esql-validation-autocomplete.esql.definitions.ceil": "将数字四舍五入为最近的整数。",
|
||||
"kbn-esql-validation-autocomplete.esql.definitions.cidr_match": "如果提供的 IP 包含在所提供的其中一个 CIDR 块中,则返回 true。",
|
||||
|
@ -5869,7 +5868,6 @@
|
|||
"kbn-esql-validation-autocomplete.esql.validation.unsupportedFunctionForCommand": "{command} 不支持函数 {name}",
|
||||
"kbn-esql-validation-autocomplete.esql.validation.unsupportedFunctionforCommandOption": "{command} {option} 不支持函数 {name}",
|
||||
"kbn-esql-validation-autocomplete.esql.validation.unsupportedLiteralOption": "{name} 的选项 [{value}] 无效。支持的选项:[{supportedOptions}]。",
|
||||
"kbn-esql-validation-autocomplete.esql.validation.unsupportedSetting": "不支持设置 [{setting}],应为 [{expected}]",
|
||||
"kbn-esql-validation-autocomplete.esql.validation.unsupportedSettingValue": "无法识别 {command} 的值 [{value}],模式需要为 [{expected}] 之一",
|
||||
"kbn-esql-validation-autocomplete.esql.validation.unsupportedTypeForCommand": "{command} 不支持表达式 [{value}] 中的 [{type}]",
|
||||
"kbn-esql-validation-autocomplete.esql.validation.wildcardNotSupportedForCommand": "不允许在 {command} 中使用通配符 (*) [{value}]",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue