[8.x] [ES|QL] Remove command option definitions (#215425) (#215542)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[ES|QL] Remove command option definitions
(#215425)](https://github.com/elastic/kibana/pull/215425)

<!--- 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-21T11:04:33Z","message":"[ES|QL]
Remove command option definitions (#215425)\n\n## Summary\n\nThis PR
removes the declarative objects that were meant to describe
the\nbehavior of \"options\" (see details section below if you don't
know what\nI'm talking about). **It does not remove \"options\" as a
concept from our\nAST.** \"Option\" is probably the wrong name for the
subcommands in the\nAST but, at the moment, it is working fine how it
is.\n\nHere is a list of what these definitions were being used for and
where I\nended up.\n\n| Use | How it worked | What I did
|\n\n|---------------------------------------------------------------------|---------------|------------|\n|
To generate command declarations for display in suggestions menu |
It\nhad some complex logic to try to construct a declaration string from
the\ninformation in the `signature` property | I replaced this
with\nstatically declared declaration strings on the command
definitions. I\ntook most of them directly from our docs. They are a
better result than\nthe autogenerated stuff |\n| To build the `METADATA`
suggestion | the definition was passed into\n`buildOptionDefinition` | I
declared the `METADATA` suggestion\nstatically in the `FROM`
autocomplete code. |\n| To check for field correctness in `METADATA` |
This logic lived in the\noption definition's `validate` method | I moved
it to the `FROM`\ncommand's validate method |\n| To validate the type of
the value assigned to `APPEND_SEPARATOR` in\n`DISSECT` | This logic
lived in the option definition's `validate`\nmethod | I moved it to the
`DISSECT` command's validate method |\n| To check if the left side of
the equals sign in `DISSECT` is\n\"APPEND_SEPARATOR | In most cases, the
parser catches stuff like this,\nbut for some reason `DISSECT`'s grammar
is very loose so we have been\nstepping in with our own validation
(maybe we should suggest changing\nthis). This was the only case that
was triggering the \"Unknown option\"\nmessage. | I moved it to the
`DISSECT` command's validate method |\n| To prevent default column
validation for `METADATA` | This was the\nonly true use of the
`skipCommonValidation` property which would prevent\nthe validator
trying to find metadata fields in the standard field list\n| I inserted
an option name check directly into the validation code.\nIt's not a good
long-term solution, but it is actually an improvement\nsince the former
code pretended to be general but was actually just for\n`METADATA`. At
least now it is clear what the exception is for. |\n| To filter
functions and operators that are available after `BY` |\nFunction
definitions sometimes declare that they are supported in a
`by`\nstatement. The validator checks if the function does. | This
didn't\nchange. The option nodes in the AST are still there and we are
still\nrelying on the `supportedCommands` and `supportedOptions`
properties in\nthe function definitions. |\n\n#### Pictures\n\n<img
width=\"859\" alt=\"Screenshot 2025-03-20 at 1 47
36 PM\"\nsrc=\"https://github.com/user-attachments/assets/3bd3c3c6-6066-466e-b33b-9444ab58670a\"\n/>\n\n_New,
statically-defined declarations_\n\n<img width=\"783\" alt=\"Screenshot
2025-03-20 at 2 12
28 PM\"\nsrc=\"https://github.com/user-attachments/assets/94550b25-5da9-4c82-9586-11b3515debd7\"\n/>\n\n_In
cases besides `APPEND_SEPARATOR`, incorrect keywords produce
syntax\nerrors._\n\n<img width=\"700\" alt=\"Screenshot 2025-03-20 at 2
09
05 PM\"\nsrc=\"https://github.com/user-attachments/assets/de1a23f4-2509-4c6e-84ec-a807e96b65a5\"\n/>\n\n_Didn't
break the `APPEND_SEPARATOR` datatype validation_\n\n<img width=\"791\"
alt=\"Screenshot 2025-03-20 at 2 03
28 PM\"\nsrc=\"https://github.com/user-attachments/assets/169aaa15-52f3-4d22-ab77-26a560cd9359\"\n/>\n\n_Didn't
break `METADATA` fields validation_\n\n### Checklist\n\nCheck the PR
satisfies following conditions. \n\nReviewers should verify this PR
satisfies this list as well.\n\n- [x] Any text added follows [EUI's
writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\nsentence case text and includes
[i18n\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\n-
[x]\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\nwas
added for features that require explanation or tutorials\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###
Background/details\n\nTill now, \"options\" have been a concept in our
code. Their definition\nisn't clear, but it essentially comes down to
any capitalized keyword\nafter the command name. For example `STATS...
>BY<`, `DISSECT...\n>APPEND_SEPARATOR<`. You could think of them as
roughly subcommands or\nsubstatements.\n\nThere was a hope that commands
would be uniform enough that these\n\"options\" would deserve to be
their own special first-class citizen. But\nthey break
conceptually...\n\nFor example `APPEND_SEPARATOR` is not a keyword with
an expression after\nit... it is a variable `APPEND_SEPARATOR=\":\"`...
or filtering in\nstats.... `STATS AVG(bytes) >WHERE<` .... so is WHERE
an option now?\n\n`FORK` will break this even further.\n\nSo, we are
moving the architecture to allow for complexity and variance\namong the
commands. Command-specific logic will have the final say in\nhow
autocomplete and validation work for anything with that
command.","sha":"b7854a8759ca91255fe318c8d7a33b91996bf990","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","Feature:ES|QL","Team:ESQL","backport:version","v9.1.0","v8.19.0"],"title":"[ES|QL]
Remove command option
definitions","number":215425,"url":"https://github.com/elastic/kibana/pull/215425","mergeCommit":{"message":"[ES|QL]
Remove command option definitions (#215425)\n\n## Summary\n\nThis PR
removes the declarative objects that were meant to describe
the\nbehavior of \"options\" (see details section below if you don't
know what\nI'm talking about). **It does not remove \"options\" as a
concept from our\nAST.** \"Option\" is probably the wrong name for the
subcommands in the\nAST but, at the moment, it is working fine how it
is.\n\nHere is a list of what these definitions were being used for and
where I\nended up.\n\n| Use | How it worked | What I did
|\n\n|---------------------------------------------------------------------|---------------|------------|\n|
To generate command declarations for display in suggestions menu |
It\nhad some complex logic to try to construct a declaration string from
the\ninformation in the `signature` property | I replaced this
with\nstatically declared declaration strings on the command
definitions. I\ntook most of them directly from our docs. They are a
better result than\nthe autogenerated stuff |\n| To build the `METADATA`
suggestion | the definition was passed into\n`buildOptionDefinition` | I
declared the `METADATA` suggestion\nstatically in the `FROM`
autocomplete code. |\n| To check for field correctness in `METADATA` |
This logic lived in the\noption definition's `validate` method | I moved
it to the `FROM`\ncommand's validate method |\n| To validate the type of
the value assigned to `APPEND_SEPARATOR` in\n`DISSECT` | This logic
lived in the option definition's `validate`\nmethod | I moved it to the
`DISSECT` command's validate method |\n| To check if the left side of
the equals sign in `DISSECT` is\n\"APPEND_SEPARATOR | In most cases, the
parser catches stuff like this,\nbut for some reason `DISSECT`'s grammar
is very loose so we have been\nstepping in with our own validation
(maybe we should suggest changing\nthis). This was the only case that
was triggering the \"Unknown option\"\nmessage. | I moved it to the
`DISSECT` command's validate method |\n| To prevent default column
validation for `METADATA` | This was the\nonly true use of the
`skipCommonValidation` property which would prevent\nthe validator
trying to find metadata fields in the standard field list\n| I inserted
an option name check directly into the validation code.\nIt's not a good
long-term solution, but it is actually an improvement\nsince the former
code pretended to be general but was actually just for\n`METADATA`. At
least now it is clear what the exception is for. |\n| To filter
functions and operators that are available after `BY` |\nFunction
definitions sometimes declare that they are supported in a
`by`\nstatement. The validator checks if the function does. | This
didn't\nchange. The option nodes in the AST are still there and we are
still\nrelying on the `supportedCommands` and `supportedOptions`
properties in\nthe function definitions. |\n\n#### Pictures\n\n<img
width=\"859\" alt=\"Screenshot 2025-03-20 at 1 47
36 PM\"\nsrc=\"https://github.com/user-attachments/assets/3bd3c3c6-6066-466e-b33b-9444ab58670a\"\n/>\n\n_New,
statically-defined declarations_\n\n<img width=\"783\" alt=\"Screenshot
2025-03-20 at 2 12
28 PM\"\nsrc=\"https://github.com/user-attachments/assets/94550b25-5da9-4c82-9586-11b3515debd7\"\n/>\n\n_In
cases besides `APPEND_SEPARATOR`, incorrect keywords produce
syntax\nerrors._\n\n<img width=\"700\" alt=\"Screenshot 2025-03-20 at 2
09
05 PM\"\nsrc=\"https://github.com/user-attachments/assets/de1a23f4-2509-4c6e-84ec-a807e96b65a5\"\n/>\n\n_Didn't
break the `APPEND_SEPARATOR` datatype validation_\n\n<img width=\"791\"
alt=\"Screenshot 2025-03-20 at 2 03
28 PM\"\nsrc=\"https://github.com/user-attachments/assets/169aaa15-52f3-4d22-ab77-26a560cd9359\"\n/>\n\n_Didn't
break `METADATA` fields validation_\n\n### Checklist\n\nCheck the PR
satisfies following conditions. \n\nReviewers should verify this PR
satisfies this list as well.\n\n- [x] Any text added follows [EUI's
writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\nsentence case text and includes
[i18n\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\n-
[x]\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\nwas
added for features that require explanation or tutorials\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###
Background/details\n\nTill now, \"options\" have been a concept in our
code. Their definition\nisn't clear, but it essentially comes down to
any capitalized keyword\nafter the command name. For example `STATS...
>BY<`, `DISSECT...\n>APPEND_SEPARATOR<`. You could think of them as
roughly subcommands or\nsubstatements.\n\nThere was a hope that commands
would be uniform enough that these\n\"options\" would deserve to be
their own special first-class citizen. But\nthey break
conceptually...\n\nFor example `APPEND_SEPARATOR` is not a keyword with
an expression after\nit... it is a variable `APPEND_SEPARATOR=\":\"`...
or filtering in\nstats.... `STATS AVG(bytes) >WHERE<` .... so is WHERE
an option now?\n\n`FORK` will break this even further.\n\nSo, we are
moving the architecture to allow for complexity and variance\namong the
commands. Command-specific logic will have the final say in\nhow
autocomplete and validation work for anything with that
command.","sha":"b7854a8759ca91255fe318c8d7a33b91996bf990"}},"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/215425","number":215425,"mergeCommit":{"message":"[ES|QL]
Remove command option definitions (#215425)\n\n## Summary\n\nThis PR
removes the declarative objects that were meant to describe
the\nbehavior of \"options\" (see details section below if you don't
know what\nI'm talking about). **It does not remove \"options\" as a
concept from our\nAST.** \"Option\" is probably the wrong name for the
subcommands in the\nAST but, at the moment, it is working fine how it
is.\n\nHere is a list of what these definitions were being used for and
where I\nended up.\n\n| Use | How it worked | What I did
|\n\n|---------------------------------------------------------------------|---------------|------------|\n|
To generate command declarations for display in suggestions menu |
It\nhad some complex logic to try to construct a declaration string from
the\ninformation in the `signature` property | I replaced this
with\nstatically declared declaration strings on the command
definitions. I\ntook most of them directly from our docs. They are a
better result than\nthe autogenerated stuff |\n| To build the `METADATA`
suggestion | the definition was passed into\n`buildOptionDefinition` | I
declared the `METADATA` suggestion\nstatically in the `FROM`
autocomplete code. |\n| To check for field correctness in `METADATA` |
This logic lived in the\noption definition's `validate` method | I moved
it to the `FROM`\ncommand's validate method |\n| To validate the type of
the value assigned to `APPEND_SEPARATOR` in\n`DISSECT` | This logic
lived in the option definition's `validate`\nmethod | I moved it to the
`DISSECT` command's validate method |\n| To check if the left side of
the equals sign in `DISSECT` is\n\"APPEND_SEPARATOR | In most cases, the
parser catches stuff like this,\nbut for some reason `DISSECT`'s grammar
is very loose so we have been\nstepping in with our own validation
(maybe we should suggest changing\nthis). This was the only case that
was triggering the \"Unknown option\"\nmessage. | I moved it to the
`DISSECT` command's validate method |\n| To prevent default column
validation for `METADATA` | This was the\nonly true use of the
`skipCommonValidation` property which would prevent\nthe validator
trying to find metadata fields in the standard field list\n| I inserted
an option name check directly into the validation code.\nIt's not a good
long-term solution, but it is actually an improvement\nsince the former
code pretended to be general but was actually just for\n`METADATA`. At
least now it is clear what the exception is for. |\n| To filter
functions and operators that are available after `BY` |\nFunction
definitions sometimes declare that they are supported in a
`by`\nstatement. The validator checks if the function does. | This
didn't\nchange. The option nodes in the AST are still there and we are
still\nrelying on the `supportedCommands` and `supportedOptions`
properties in\nthe function definitions. |\n\n#### Pictures\n\n<img
width=\"859\" alt=\"Screenshot 2025-03-20 at 1 47
36 PM\"\nsrc=\"https://github.com/user-attachments/assets/3bd3c3c6-6066-466e-b33b-9444ab58670a\"\n/>\n\n_New,
statically-defined declarations_\n\n<img width=\"783\" alt=\"Screenshot
2025-03-20 at 2 12
28 PM\"\nsrc=\"https://github.com/user-attachments/assets/94550b25-5da9-4c82-9586-11b3515debd7\"\n/>\n\n_In
cases besides `APPEND_SEPARATOR`, incorrect keywords produce
syntax\nerrors._\n\n<img width=\"700\" alt=\"Screenshot 2025-03-20 at 2
09
05 PM\"\nsrc=\"https://github.com/user-attachments/assets/de1a23f4-2509-4c6e-84ec-a807e96b65a5\"\n/>\n\n_Didn't
break the `APPEND_SEPARATOR` datatype validation_\n\n<img width=\"791\"
alt=\"Screenshot 2025-03-20 at 2 03
28 PM\"\nsrc=\"https://github.com/user-attachments/assets/169aaa15-52f3-4d22-ab77-26a560cd9359\"\n/>\n\n_Didn't
break `METADATA` fields validation_\n\n### Checklist\n\nCheck the PR
satisfies following conditions. \n\nReviewers should verify this PR
satisfies this list as well.\n\n- [x] Any text added follows [EUI's
writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\nsentence case text and includes
[i18n\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\n-
[x]\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\nwas
added for features that require explanation or tutorials\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###
Background/details\n\nTill now, \"options\" have been a concept in our
code. Their definition\nisn't clear, but it essentially comes down to
any capitalized keyword\nafter the command name. For example `STATS...
>BY<`, `DISSECT...\n>APPEND_SEPARATOR<`. You could think of them as
roughly subcommands or\nsubstatements.\n\nThere was a hope that commands
would be uniform enough that these\n\"options\" would deserve to be
their own special first-class citizen. But\nthey break
conceptually...\n\nFor example `APPEND_SEPARATOR` is not a keyword with
an expression after\nit... it is a variable `APPEND_SEPARATOR=\":\"`...
or filtering in\nstats.... `STATS AVG(bytes) >WHERE<` .... so is WHERE
an option now?\n\n`FORK` will break this even further.\n\nSo, we are
moving the architecture to allow for complexity and variance\namong the
commands. Command-specific logic will have the final say in\nhow
autocomplete and validation work for anything with that
command.","sha":"b7854a8759ca91255fe318c8d7a33b91996bf990"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->
This commit is contained in:
Drew Tate 2025-03-24 02:29:55 -06:00 committed by GitHub
parent 95782f6d3a
commit 315e9b19ca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 213 additions and 429 deletions

View file

@ -8,12 +8,7 @@
*/
export type { SuggestionRawDefinition, ItemKind } from './src/autocomplete/types';
export type {
FunctionDefinition,
CommandDefinition,
CommandOptionsDefinition,
Literals,
} from './src/definitions/types';
export type { FunctionDefinition, CommandDefinition, Literals } from './src/definitions/types';
export type { ESQLCallbacks } from './src/shared/types';
/**
@ -44,7 +39,6 @@ export {
getFunctionDefinition,
getCommandDefinition,
getAllCommands,
getCommandOption,
getColumnForASTNode as lookupColumn,
shouldBeQuotedText,
printFunctionSignature,

View file

@ -77,7 +77,7 @@ describe('autocomplete.suggest', () => {
});
const { assertSuggestions } = await setup();
const expected = [
'METADATA $0',
'METADATA ',
',',
'| ',
...recommendedQueries.map((query) => query.queryString),
@ -89,7 +89,7 @@ describe('autocomplete.suggest', () => {
test('partially-typed METADATA keyword', async () => {
const { assertSuggestions } = await setup();
assertSuggestions('FROM index1 MET/', ['METADATA $0']);
assertSuggestions('FROM index1 MET/', ['METADATA ']);
});
test('not before first index', async () => {

View file

@ -319,7 +319,7 @@ describe('autocomplete', () => {
// FROM source METADATA
recommendedQuerySuggestions = getRecommendedQueriesSuggestions('', 'dateField');
testSuggestions('FROM index1 M/', ['METADATA $0']);
testSuggestions('FROM index1 M/', ['METADATA ']);
// FROM source METADATA field
testSuggestions('FROM index1 METADATA _/', METADATA_FIELDS);
@ -570,7 +570,7 @@ describe('autocomplete', () => {
testSuggestions('FROM a /', [
attachTriggerCommand('| '),
',',
attachAsSnippet(attachTriggerCommand('METADATA $0')),
attachTriggerCommand('METADATA '),
...recommendedQuerySuggestions.map((q) => q.queryString),
]);
@ -675,7 +675,6 @@ describe('autocomplete', () => {
{
text: 'foo$bar METADATA ',
filterText: 'foo$bar',
asSnippet: false, // important because the text includes "$"
command: TRIGGER_SUGGESTION_COMMAND,
rangeToReplace: { start: 6, end: 13 },
},
@ -708,7 +707,7 @@ describe('autocomplete', () => {
recommendedQuerySuggestions = getRecommendedQueriesSuggestions('', 'dateField');
// FROM source METADATA
testSuggestions('FROM index1 M/', [attachAsSnippet(attachTriggerCommand('METADATA $0'))]);
testSuggestions('FROM index1 M/', [attachTriggerCommand('METADATA ')]);
describe('ENRICH', () => {
testSuggestions(

View file

@ -8,8 +8,8 @@
*/
import { ESQLCommandOption } from '@kbn/esql-ast';
import { i18n } from '@kbn/i18n';
import { isMarkerNode } from '../../../shared/context';
import { metadataOption } from '../../../definitions/options';
import type { SuggestionRawDefinition } from '../../types';
import { getOverlapRange, handleFragment, removeQuoteForSuggestedSources } from '../../helper';
import { CommandSuggestParams } from '../../../definitions/types';
@ -23,7 +23,6 @@ import {
import {
TRIGGER_SUGGESTION_COMMAND,
buildFieldsDefinitions,
buildOptionDefinition,
buildSourcesDefinitions,
} from '../../factories';
import { ESQLSourceResult } from '../../../shared/types';
@ -70,7 +69,7 @@ export async function suggest({
}
// FROM something /
else if (indexes.length > 0 && /\s$/.test(innerText) && !isRestartingExpression(innerText)) {
suggestions.push(buildOptionDefinition(metadataOption));
suggestions.push(metadataSuggestion);
suggestions.push(commaCompleteItem);
suggestions.push(pipeCompleteItem);
suggestions.push(...(await getRecommendedQueriesSuggestions()));
@ -81,7 +80,7 @@ export async function suggest({
/^FROM\s+\S+\s+/i.test(innerText) &&
metadataOverlap.start !== metadataOverlap.end
) {
suggestions.push(buildOptionDefinition(metadataOption));
suggestions.push(metadataSuggestion);
}
// FROM someth/
// FROM something/
@ -127,10 +126,9 @@ export async function suggest({
rangeToReplace,
},
{
...buildOptionDefinition(metadataOption),
...metadataSuggestion,
filterText: fragment,
text: fragment + ' METADATA ',
asSnippet: false, // turn this off because $ could be contained within the source name
rangeToReplace,
},
...recommendedQuerySuggestions.map((suggestion) => ({
@ -161,6 +159,17 @@ function getSourceSuggestions(sources: ESQLSourceResult[]) {
);
}
const metadataSuggestion: SuggestionRawDefinition = {
label: 'METADATA',
text: 'METADATA ',
kind: 'Reference',
detail: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.metadataDoc', {
defaultMessage: 'Metadata',
}),
sortText: '1',
command: TRIGGER_SUGGESTION_COMMAND,
};
async function suggestForMetadata(metadata: ESQLCommandOption, innerText: string) {
const existingFields = new Set(metadata.args.filter(isColumnItem).map(({ name }) => name));
const filteredMetaFields = METADATA_FIELDS.filter((name) => !existingFields.has(name));

View file

@ -13,7 +13,6 @@ import { operatorsDefinitions } from '../definitions/all_operators';
import { getOperatorSuggestion, TRIGGER_SUGGESTION_COMMAND } from './factories';
import { CommandDefinition, CommandTypeDefinition } from '../definitions/types';
import { getCommandDefinition } from '../shared/helpers';
import { getCommandSignature } from '../definitions/helpers';
import { buildDocumentation } from './documentation_util';
const techPreviewLabel = i18n.translate(
@ -55,7 +54,6 @@ export const getCommandAutocompleteDefinitions = (
if (commandDefinition.preview) {
detail = `[${techPreviewLabel}] ${detail}`;
}
const commandSignature = getCommandSignature(commandDefinition, type.name);
const suggestion: SuggestionRawDefinition = {
label: type.name ? `${type.name.toLocaleUpperCase()} ${label}` : label,
text: type.name ? `${type.name.toLocaleUpperCase()} ${text}` : text,
@ -63,7 +61,7 @@ export const getCommandAutocompleteDefinitions = (
kind: 'Method',
detail,
documentation: {
value: buildDocumentation(commandSignature.declaration, commandSignature.examples),
value: buildDocumentation(commandDefinition.declaration, commandDefinition.examples),
},
sortText: 'A-' + label + '-' + type.name,
command: TRIGGER_SUGGESTION_COMMAND,

View file

@ -18,7 +18,6 @@ import { getFunctionSignatures } from '../definitions/helpers';
import { timeUnitsToSuggest } from '../definitions/literals';
import {
FunctionDefinition,
CommandOptionsDefinition,
FunctionParameterType,
FunctionDefinitionTypes,
} from '../definitions/types';
@ -366,28 +365,6 @@ export const buildPoliciesDefinitions = (
command: TRIGGER_SUGGESTION_COMMAND,
}));
/** @deprecated — options will be removed */
export const buildOptionDefinition = (
option: CommandOptionsDefinition,
isAssignType: boolean = false
) => {
const completeItem: SuggestionRawDefinition = {
label: option.name.toUpperCase(),
text: option.name.toUpperCase(),
kind: 'Reference',
detail: option.description,
sortText: '1',
};
if (isAssignType || option.signature.params.length) {
completeItem.text = isAssignType
? `${option.name.toUpperCase()} = $0`
: `${option.name.toUpperCase()} $0`;
completeItem.asSnippet = true;
completeItem.command = TRIGGER_SUGGESTION_COMMAND;
}
return completeItem;
};
export function getUnitDuration(unit: number = 1) {
const filteredTimeLiteral = timeUnitsToSuggest.filter(({ name }) => {
const result = /s$/.test(name);

View file

@ -16,22 +16,18 @@ import {
isFunctionExpression,
isWhereExpression,
ESQLCommandMode,
ESQLCommandOption,
} from '@kbn/esql-ast';
import {
isAssignment,
isColumnItem,
isFunctionItem,
isInlineCastItem,
isLiteralItem,
isOptionItem,
isSingleItem,
noCaseCompare,
} from '../shared/helpers';
import {
appendSeparatorOption,
asOption,
byOption,
metadataOption,
onOption,
withOption,
} from './options';
import { type CommandDefinition } from './types';
import { ENRICH_MODES, checkAggExistence, checkFunctionContent } from './commands_helpers';
@ -54,6 +50,7 @@ import { suggest as suggestForStats } from '../autocomplete/commands/stats';
import { suggest as suggestForWhere } from '../autocomplete/commands/where';
import { getMessageFromId } from '../validation/errors';
import { METADATA_FIELDS } from '../shared/constants';
const statsValidator = (command: ESQLCommand) => {
const messages: ESQLMessage[] = [];
@ -147,6 +144,7 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
defaultMessage:
'Produces a row with one or more columns with values that you specify. This can be useful for testing.',
}),
declaration: 'ROW column1 = value1[, ..., columnN = valueN]',
examples: ['ROW a=1', 'ROW a=1, b=2'],
signature: {
multipleParams: true,
@ -154,7 +152,6 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
params: [{ name: 'assignment', type: 'any' }],
},
suggest: suggestForRow,
options: [],
},
{
name: 'from',
@ -162,21 +159,49 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
defaultMessage:
'Retrieves data from one or more data streams, indices, or aliases. In a query or subquery, you must use the from command first and it does not need a leading pipe. For example, to retrieve data from an index:',
}),
examples: ['from logs', 'from logs-*', 'from logs_*, events-*'],
options: [metadataOption],
declaration: 'FROM index_pattern [METADATA fields]',
examples: ['FROM logs', 'FROM logs-*', 'FROM logs_*, events-*'],
signature: {
multipleParams: true,
params: [{ name: 'index', type: 'source', wildcards: true }],
},
suggest: suggestForFrom,
validate: (command: ESQLCommand) => {
const metadataStatement = command.args.find(
(arg) => isOptionItem(arg) && arg.name === 'metadata'
) as ESQLCommandOption | undefined;
if (!metadataStatement) {
return [];
}
const messages: ESQLMessage[] = [];
const fields = metadataStatement.args.filter(isColumnItem);
for (const field of fields) {
if (!METADATA_FIELDS.includes(field.name)) {
messages.push(
getMessageFromId({
messageId: 'unknownMetadataField',
values: {
value: field.name,
availableFields: Array.from(METADATA_FIELDS).join(', '),
},
locations: field.location,
})
);
}
}
return messages;
},
},
{
name: 'show',
description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.showDoc', {
defaultMessage: 'Returns information about the deployment and its capabilities',
}),
declaration: 'SHOW item',
examples: ['SHOW INFO'],
options: [],
signature: {
multipleParams: false,
params: [{ name: 'functions', type: 'function' }],
@ -195,16 +220,16 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
'The command returns only the fields in the aggregation, and you can use a wide range of statistical functions with the stats command. ' +
'When you perform more than one aggregation, separate each aggregation with a comma.',
}),
declaration: '',
examples: [
'metrics index',
'metrics index, index2',
'metrics index avg = avg(a)',
'metrics index sum(b) by b',
'metrics index, index2 sum(b) by b % 2',
'metrics <sources> [ <aggregates> [ by <grouping> ]]',
'metrics src1, src2 agg1, agg2 by field1, field2',
'METRICS index',
'METRICS index, index2',
'METRICS index avg = avg(a)',
'METRICS index sum(b) by b',
'METRICS index, index2 sum(b) by b % 2',
'METRICS <sources> [ <aggregates> [ by <grouping> ]]',
'METRICS src1, src2 agg1, agg2 by field1, field2',
],
options: [],
signature: {
multipleParams: true,
params: [
@ -220,12 +245,15 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
defaultMessage:
'Calculates aggregate statistics, such as average, count, and sum, over the incoming search results set. Similar to SQL aggregation, if the stats command is used without a BY clause, only one row is returned, which is the aggregation over the entire incoming search results set. When you use a BY clause, one row is returned for each distinct value in the field specified in the BY clause. The stats command returns only the fields in the aggregation, and you can use a wide range of statistical functions with the stats command. When you perform more than one aggregation, separate each aggregation with a comma.',
}),
declaration: `STATS [column1 =] expression1 [WHERE boolean_expression1][,
...,
[columnN =] expressionN [WHERE boolean_expressionN]]
[BY grouping_expression1[, ..., grouping_expressionN]]`,
examples: ['… | stats avg = avg(a)', '… | stats sum(b) by b', '… | stats sum(b) by b % 2'],
signature: {
multipleParams: true,
params: [{ name: 'expression', type: 'function', optional: true }],
},
options: [byOption],
validate: statsValidator,
suggest: suggestForStats,
},
@ -239,12 +267,12 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
'Calculates an aggregate result and merges that result back into the stream of input data. Without the optional `BY` clause this will produce a single result which is appended to each row. With a `BY` clause this will produce one result per grouping and merge the result into the stream based on matching group keys.',
}
),
declaration: '',
examples: ['… | EVAL bar = a * b | INLINESTATS m = MAX(bar) BY b'],
signature: {
multipleParams: true,
params: [{ name: 'expression', type: 'function', optional: true }],
},
options: [byOption],
// Reusing the same validation logic as stats command
validate: statsValidator,
suggest: () => [],
@ -256,17 +284,17 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
defaultMessage:
'Calculates an expression and puts the resulting value into a search results field.',
}),
declaration: 'EVAL [column1 =] value1[, ..., [columnN =] valueN]',
examples: [
'… | eval b * c',
'… | eval a = b * c',
'… | eval then = now() + 1 year + 2 weeks',
'… | eval a = b * c, d = e * f',
'… | EVAL b * c',
'… | EVAL a = b * c',
'… | EVAL then = NOW() + 1 year + 2 weeks',
'… | EVAL a = b * c, d = e * f',
],
signature: {
multipleParams: true,
params: [{ name: 'expression', type: 'any' }],
},
options: [],
suggest: suggestForEval,
},
{
@ -274,12 +302,12 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.renameDoc', {
defaultMessage: 'Renames an old column to a new one',
}),
examples: ['… | rename old as new', '… | rename old as new, a as b'],
declaration: 'RENAME old_name1 AS new_name1[, ..., old_nameN AS new_nameN]',
examples: ['… | RENAME old AS new', '… | RENAME old AS new, a AS b'],
signature: {
multipleParams: true,
params: [{ name: 'renameClause', type: 'column' }],
},
options: [asOption],
suggest: suggestForRename,
},
{
@ -288,12 +316,12 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
defaultMessage:
'Returns the first search results, in search order, based on the "limit" specified.',
}),
examples: ['… | limit 100', '… | limit 0'],
declaration: 'LIMIT max_number_of_rows',
examples: ['… | LIMIT 100', '… | LIMIT 1'],
signature: {
multipleParams: false,
params: [{ name: 'size', type: 'integer', constantOnly: true }],
},
options: [],
suggest: suggestForLimit,
},
{
@ -302,9 +330,9 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
defaultMessage:
'Rearranges fields in the Results table by applying the keep clauses in fields',
}),
examples: ['… | keep a', '… | keep a,b'],
declaration: 'KEEP column1[, ..., columnN]',
examples: ['… | KEEP a', '… | KEEP a, b'],
suggest: suggestForKeep,
options: [],
signature: {
multipleParams: true,
params: [{ name: 'column', type: 'column', wildcards: true }],
@ -315,8 +343,8 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.dropDoc', {
defaultMessage: 'Drops columns',
}),
examples: ['… | drop a', '… | drop a,b'],
options: [],
declaration: 'DROP column1[, ..., columnN]',
examples: ['… | DROP a', '… | DROP a, b'],
signature: {
multipleParams: true,
params: [{ name: 'column', type: 'column', wildcards: true }],
@ -366,32 +394,32 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
defaultMessage:
'Sorts all results by the specified fields. By default, null values are treated as being larger than any other value. With an ascending sort order, null values are sorted last, and with a descending sort order, null values are sorted first. You can change that by providing NULLS FIRST or NULLS LAST',
}),
declaration:
'SORT column1 [ASC/DESC][NULLS FIRST/NULLS LAST][, ..., columnN [ASC/DESC][NULLS FIRST/NULLS LAST]]',
examples: [
'… | sort a desc, b nulls last, c asc nulls first',
'… | sort b nulls last',
'… | sort c asc nulls first',
'… | sort a - abs(b)',
'… | SORT a DESC, b NULLS LAST, c ASC NULLS FIRST',
'… | SORT b NULLS LAST',
'… | SORT c ASC NULLS FIRST',
'… | SORT a - abs(b)',
],
options: [],
signature: {
multipleParams: true,
params: [{ name: 'expression', type: 'any' }],
},
suggest: suggestForSort,
},
{
name: 'where',
description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.whereDoc', {
defaultMessage:
'Uses "predicate-expressions" to filter search results. A predicate expression, when evaluated, returns TRUE or FALSE. The where command only returns the results that evaluate to TRUE. For example, to filter results for a specific field value',
}),
examples: ['… | where status_code == 200'],
declaration: 'WHERE expression',
examples: ['… | WHERE status_code == 200'],
signature: {
multipleParams: false,
params: [{ name: 'expression', type: 'boolean' }],
},
options: [],
suggest: suggestForWhere,
},
{
@ -400,8 +428,8 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
defaultMessage:
'Extracts multiple string values from a single string input, based on a pattern',
}),
declaration: 'DISSECT input "pattern" [APPEND_SEPARATOR="<separator>"]',
examples: ['… | DISSECT a "%{b} %{c}" APPEND_SEPARATOR = ":"'],
options: [appendSeparatorOption],
signature: {
multipleParams: false,
params: [
@ -410,6 +438,43 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
],
},
suggest: suggestForDissect,
validate: (command: ESQLCommand) => {
const appendSeparatorClause = command.args.find((arg) => isOptionItem(arg)) as
| ESQLCommandOption
| undefined;
if (!appendSeparatorClause) {
return [];
}
if (appendSeparatorClause.name !== 'append_separator') {
return [
getMessageFromId({
messageId: 'unknownDissectKeyword',
values: { keyword: appendSeparatorClause.name },
locations: appendSeparatorClause.location,
}),
];
}
const messages: ESQLMessage[] = [];
const [firstArg] = appendSeparatorClause.args;
if (
!Array.isArray(firstArg) &&
(!isLiteralItem(firstArg) || firstArg.literalType !== 'keyword')
) {
const value =
'value' in firstArg && !isInlineCastItem(firstArg) ? firstArg.value : firstArg.name;
messages.push(
getMessageFromId({
messageId: 'wrongDissectOptionArgumentType',
values: { value: value ?? '' },
locations: firstArg.location,
})
);
}
return messages;
},
},
{
name: 'grok',
@ -417,8 +482,8 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
defaultMessage:
'Extracts multiple string values from a single string input, based on a pattern',
}),
declaration: 'GROK input "pattern"',
examples: ['… | GROK a "%{IP:b} %{NUMBER:c}"'],
options: [],
signature: {
multipleParams: false,
params: [
@ -433,8 +498,8 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.mvExpandDoc', {
defaultMessage: 'Expands multivalued fields into one row per value, duplicating other fields',
}),
examples: ['row a=[1,2,3] | mv_expand a'],
options: [],
declaration: 'MV_EXPAND column',
examples: ['ROW a=[1,2,3] | MV_EXPAND a'],
preview: true,
signature: {
multipleParams: false,
@ -448,12 +513,13 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
defaultMessage:
'Enrich table with another table. Before you can use enrich, you need to create and execute an enrich policy.',
}),
declaration:
'ENRICH policy [ON match_field] [WITH [new_name1 = ]field1, [new_name2 = ]field2, ...]',
examples: [
'… | enrich my-policy',
'… | enrich my-policy on pivotField',
'… | enrich my-policy on pivotField with a = enrichFieldA, b = enrichFieldB',
'… | ENRICH my-policy',
'… | ENRICH my-policy ON pivotField',
'… | ENRICH my-policy ON pivotField WITH a = enrichFieldA, b = enrichFieldB',
],
options: [onOption, withOption],
signature: {
multipleParams: false,
params: [{ name: 'policyName', type: 'source', innerTypes: ['policy'] }],
@ -489,9 +555,9 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
{
name: 'hidden_command',
description: 'A test fixture to test hidden-ness',
declaration: '',
hidden: true,
examples: [],
options: [],
signature: {
params: [],
multipleParams: false,
@ -535,6 +601,7 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.joinDoc', {
defaultMessage: 'Join table with another table.',
}),
declaration: `LOOKUP JOIN <lookup_index> ON <field_name>`,
preview: true,
examples: [
'… | LOOKUP JOIN lookup_index ON join_field',
@ -547,7 +614,6 @@ export const commandDefinitions: Array<CommandDefinition<any>> = [
multipleParams: true,
params: [{ name: 'index', type: 'source', wildcards: true }],
},
options: [onOption],
suggest: suggestForJoin,
},
];

View file

@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import type { CommandDefinition, FunctionDefinition, FunctionParameterType } from './types';
import type { FunctionDefinition, FunctionParameterType } from './types';
/**
* Given a function definition, this function will return a list of function signatures
@ -55,52 +55,6 @@ function handleAdditionalArgs(
: '';
}
export function getCommandSignature(
{ name, signature, options, examples }: CommandDefinition<string>,
typeName?: string,
{ withTypes }: { withTypes: boolean } = { withTypes: true }
) {
const commandName = typeName
? `${typeName.toUpperCase()} ${name.toUpperCase()}`
: name.toUpperCase();
return {
declaration: `${commandName} ${printCommandArguments(signature, withTypes)} ${(
options || []
).map(
(option) =>
`${
option.wrapped ? option.wrapped[0] : ''
}${option.name.toUpperCase()} ${printCommandArguments(option.signature, withTypes)}${
option.wrapped ? option.wrapped[1] : ''
}`
)}`,
examples,
};
}
function printCommandArguments(
{ multipleParams, params }: CommandDefinition<string>['signature'],
withTypes: boolean
): string {
return `${params.map((arg) => printCommandArgument(arg, withTypes)).join(', `')}${
multipleParams
? ` ,[...${params.map((arg) => printCommandArgument(arg, withTypes)).join(', `')}]`
: ''
}`;
}
function printCommandArgument(
param: CommandDefinition<string>['signature']['params'][number],
withTypes: boolean
): string {
if (!withTypes) {
return param.name || '';
}
return `${param.name}${param.optional ? ':?' : ':'} ${param.type}${
param.innerTypes ? `{${param.innerTypes}}` : ''
}`;
}
export function printArguments(
{
name,

View file

@ -1,152 +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 { ESQLCommandOption, ESQLMessage } from '@kbn/esql-ast';
import { isLiteralItem, isColumnItem, isInlineCastItem } from '../shared/helpers';
import { getMessageFromId } from '../validation/errors';
import type { CommandOptionsDefinition } from './types';
/** @deprecated — options are going away */
export const byOption: CommandOptionsDefinition = {
name: 'by',
description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.byDoc', {
defaultMessage: 'By',
}),
signature: {
multipleParams: true,
params: [{ name: 'expression', type: 'any' }],
},
optional: true,
};
/** @deprecated — options are going away */
export const metadataOption: CommandOptionsDefinition = {
name: 'metadata',
description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.metadataDoc', {
defaultMessage: 'Metadata',
}),
signature: {
multipleParams: true,
params: [{ name: 'column', type: 'column' }],
},
optional: true,
skipCommonValidation: true,
validate: (option, command, references) => {
const messages: ESQLMessage[] = [];
// need to test the parent command here
if (/\[metadata/i.test(command.text)) {
messages.push(
getMessageFromId({
messageId: 'metadataBracketsDeprecation',
values: {},
locations: option.location,
})
);
}
const fields = option.args.filter(isColumnItem);
const metadataFieldsAvailable = references as unknown as Set<string>;
if (metadataFieldsAvailable.size > 0) {
for (const field of fields) {
if (!metadataFieldsAvailable.has(field.name)) {
messages.push(
getMessageFromId({
messageId: 'unknownMetadataField',
values: {
value: field.name,
availableFields: Array.from(metadataFieldsAvailable).join(', '),
},
locations: field.location,
})
);
}
}
}
return messages;
},
};
/** @deprecated — options are going away */
export const asOption: CommandOptionsDefinition = {
name: 'as',
description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.asDoc', {
defaultMessage: 'As',
}),
signature: {
multipleParams: false,
params: [
{ name: 'oldName', type: 'column' },
{ name: 'newName', type: 'column' },
],
},
optional: false,
};
/** @deprecated — options are going away */
export const onOption: CommandOptionsDefinition = {
name: 'on',
description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.onDoc', {
defaultMessage: 'On',
}),
signature: {
multipleParams: false,
params: [{ name: 'matchingColumn', type: 'column' }],
},
optional: true,
};
/** @deprecated — options are going away */
export const withOption: CommandOptionsDefinition = {
name: 'with',
description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.withDoc', {
defaultMessage: 'With',
}),
signature: {
multipleParams: true,
params: [{ name: 'assignment', type: 'any' }],
},
optional: true,
};
/** @deprecated — options are going away */
export const appendSeparatorOption: CommandOptionsDefinition = {
name: 'append_separator',
description: i18n.translate(
'kbn-esql-validation-autocomplete.esql.definitions.appendSeparatorDoc',
{
defaultMessage:
'The character(s) that separate the appended fields. Default to empty string ("").',
}
),
signature: {
multipleParams: false,
params: [{ name: 'separator', type: 'string' }],
},
optional: true,
skipCommonValidation: true, // tell the validation engine to use only the validate function here
validate: (option: ESQLCommandOption) => {
const messages: ESQLMessage[] = [];
const [firstArg] = option.args;
if (
!Array.isArray(firstArg) &&
(!isLiteralItem(firstArg) || firstArg.literalType !== 'keyword')
) {
const value =
'value' in firstArg && !isInlineCastItem(firstArg) ? firstArg.value : firstArg.name;
messages.push(
getMessageFromId({
messageId: 'wrongDissectOptionArgumentType',
values: { value: value ?? '' },
locations: firstArg.location,
})
);
}
return messages;
},
};

View file

@ -9,7 +9,6 @@
import type {
ESQLAstItem,
ESQLCommand,
ESQLCommandOption,
ESQLFunction,
ESQLMessage,
ESQLSource,
@ -280,12 +279,16 @@ export type CommandSuggestFunction<CommandName extends string> = (
params: CommandSuggestParams<CommandName>
) => Promise<SuggestionRawDefinition[]> | SuggestionRawDefinition[];
/**
* @deprecated use CommandDefinition instead
*/
export interface CommandBaseDefinition<CommandName extends string> {
export interface CommandDefinition<CommandName extends string> {
name: CommandName;
examples: string[];
/**
* The pattern for declaring this command statement.
*/
declaration: string;
/**
* Command name prefix, such as "LEFT" or "RIGHT" for JOIN command.
*/
@ -293,14 +296,29 @@ export interface CommandBaseDefinition<CommandName extends string> {
alias?: string;
description: string;
/**
* Displays a Technical preview label in the autocomplete
*/
preview?: boolean;
/**
* Whether to show or hide in autocomplete suggestion list
*/
hidden?: boolean;
/**
* This method 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[];
/**
* This method is called to load suggestions when the cursor is within this command.
*/
suggest: CommandSuggestFunction<CommandName>;
/** @deprecated this property will disappear in the future */
signature: {
multipleParams: boolean;
@ -324,43 +342,9 @@ export interface CommandTypeDefinition {
description?: string;
}
/**
* @deprecated options are going away
*/
export interface CommandOptionsDefinition<CommandName extends string = string>
extends CommandBaseDefinition<CommandName> {
wrapped?: string[];
optional: boolean;
skipCommonValidation?: boolean;
validate?: (
option: ESQLCommandOption,
command: ESQLCommand,
references?: unknown
) => ESQLMessage[];
}
export interface CommandDefinition<CommandName extends string>
extends CommandBaseDefinition<CommandName> {
examples: string[];
/**
* 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 */
options: CommandOptionsDefinition[];
}
export interface Literals {
name: string;
description: string;
}
export type SignatureType =
| FunctionDefinition['signatures'][number]
| CommandOptionsDefinition['signature'];
export type SignatureArgType = SignatureType['params'][number];
export type FunctionParameter = FunctionDefinition['signatures'][number]['params'][number];

View file

@ -32,17 +32,8 @@ import { groupingFunctionDefinitions } from '../definitions/generated/grouping_f
import { getTestFunctions } from './test_functions';
import { getFunctionSignatures } from '../definitions/helpers';
import { timeUnits } from '../definitions/literals';
import {
byOption,
metadataOption,
asOption,
onOption,
withOption,
appendSeparatorOption,
} from '../definitions/options';
import {
CommandDefinition,
CommandOptionsDefinition,
FunctionParameter,
FunctionDefinition,
FunctionParameterType,
@ -221,12 +212,6 @@ export function getAllCommands() {
return Array.from(buildCommandLookup().values());
}
export function getCommandOption(optionName: CommandOptionsDefinition<string>['name']) {
return [byOption, metadataOption, asOption, onOption, withOption, appendSeparatorOption].find(
({ name }) => name === optionName
);
}
function doesLiteralMatchParameterType(argType: FunctionParameterType, item: ESQLLiteral) {
if (item.literalType === argType) {
return true;

View file

@ -154,15 +154,17 @@ function getMessageAndTypeFromId<K extends ErrorTypes>({
}
),
};
case 'unknownOption':
case 'unknownDissectKeyword':
return {
message: i18n.translate('kbn-esql-validation-autocomplete.esql.validation.unknownOption', {
defaultMessage: 'Invalid option for {command}: [{option}]',
values: {
command: out.command,
option: out.option,
},
}),
message: i18n.translate(
'kbn-esql-validation-autocomplete.esql.validation.unknownDissectKeyword',
{
defaultMessage: 'Expected [APPEND_SEPARATOR] in [DISSECT] but found [{keyword}]',
values: {
keyword: out.keyword,
},
}
),
};
case 'unsupportedFunctionForCommand':
return {
@ -372,7 +374,7 @@ function getMessageAndTypeFromId<K extends ErrorTypes>({
'kbn-esql-validation-autocomplete.esql.validation.wrongDissectOptionArgumentType',
{
defaultMessage:
'Invalid value for DISSECT append_separator: expected a string, but was [{value}]',
'Invalid value for DISSECT APPEND_SEPARATOR: expected a string, but was [{value}]',
values: {
value: out.value,
},

View file

@ -2711,7 +2711,8 @@
{
"query": "from a_index | dissect textField \"%{firstWord}\" option ",
"error": [
"SyntaxError: mismatched input '<EOF>' expecting '='"
"SyntaxError: mismatched input '<EOF>' expecting '='",
"Expected [APPEND_SEPARATOR] in [DISSECT] but found [option]"
],
"warning": []
},
@ -2719,14 +2720,14 @@
"query": "from a_index | dissect textField \"%{firstWord}\" option = ",
"error": [
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, '['}",
"Invalid option for DISSECT: [option]"
"Expected [APPEND_SEPARATOR] in [DISSECT] but found [option]"
],
"warning": []
},
{
"query": "from a_index | dissect textField \"%{firstWord}\" option = 1",
"error": [
"Invalid option for DISSECT: [option]"
"Expected [APPEND_SEPARATOR] in [DISSECT] but found [option]"
],
"warning": []
},
@ -2738,14 +2739,14 @@
{
"query": "from a_index | dissect textField \"%{firstWord}\" ignore_missing = true",
"error": [
"Invalid option for DISSECT: [ignore_missing]"
"Expected [APPEND_SEPARATOR] in [DISSECT] but found [ignore_missing]"
],
"warning": []
},
{
"query": "from a_index | dissect textField \"%{firstWord}\" append_separator = true",
"error": [
"Invalid value for DISSECT append_separator: expected a string, but was [true]"
"Invalid value for DISSECT APPEND_SEPARATOR: expected a string, but was [true]"
],
"warning": []
},

View file

@ -123,9 +123,9 @@ export interface ValidationErrors {
message: string;
type: { command: string; type: string; typeCount: number; givenType: string; column: string };
};
unknownOption: {
unknownDissectKeyword: {
message: string;
type: { command: string; option: string };
type: { keyword: string };
};
wrongOptionArgumentType: {
message: string;

View file

@ -714,13 +714,14 @@ describe('validation logic', () => {
]);
testErrorsAndWarnings('from a_index | dissect textField "%{firstWord}" option ', [
"SyntaxError: mismatched input '<EOF>' expecting '='",
'Expected [APPEND_SEPARATOR] in [DISSECT] but found [option]',
]);
testErrorsAndWarnings('from a_index | dissect textField "%{firstWord}" option = ', [
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, '['}",
'Invalid option for DISSECT: [option]',
'Expected [APPEND_SEPARATOR] in [DISSECT] but found [option]',
]);
testErrorsAndWarnings('from a_index | dissect textField "%{firstWord}" option = 1', [
'Invalid option for DISSECT: [option]',
'Expected [APPEND_SEPARATOR] in [DISSECT] but found [option]',
]);
testErrorsAndWarnings(
'from a_index | dissect textField "%{firstWord}" append_separator = "-"',
@ -728,11 +729,11 @@ describe('validation logic', () => {
);
testErrorsAndWarnings(
'from a_index | dissect textField "%{firstWord}" ignore_missing = true',
['Invalid option for DISSECT: [ignore_missing]']
['Expected [APPEND_SEPARATOR] in [DISSECT] but found [ignore_missing]']
);
testErrorsAndWarnings(
'from a_index | dissect textField "%{firstWord}" append_separator = true',
['Invalid value for DISSECT append_separator: expected a string, but was [true]']
['Invalid value for DISSECT APPEND_SEPARATOR: expected a string, but was [true]']
);
testErrorsAndWarnings('from a_index | dissect textField "%{firstWord}" | keep firstWord', []);
// testErrorsAndWarnings('from a_index | dissect s* "%{a}"', [

View file

@ -20,8 +20,6 @@ import {
walk,
} from '@kbn/esql-ast';
import type { ESQLAstJoinCommand, ESQLIdentifier } from '@kbn/esql-ast/src/types';
import { CommandOptionsDefinition } from '../definitions/types';
import { METADATA_FIELDS } from '../shared/constants';
import { compareTypesWithLiterals } from '../shared/esql_types';
import {
areFieldAndVariableTypesCompatible,
@ -234,9 +232,8 @@ function validateCommand(
}
default: {
// Now validate arguments
for (const commandArg of command.args) {
const wrappedArg = Array.isArray(commandArg) ? commandArg : [commandArg];
for (const arg of wrappedArg) {
for (const arg of command.args) {
if (!Array.isArray(arg)) {
if (isFunctionItem(arg)) {
messages.push(
...validateFunction({
@ -249,14 +246,7 @@ function validateCommand(
})
);
} else if (isOptionItem(arg)) {
messages.push(
...validateOption(
arg,
commandDef.options.find(({ name }) => name === arg.name),
command,
references
)
);
messages.push(...validateOption(arg, command, references));
} else if (isColumnItem(arg) || isIdentifier(arg)) {
if (command.name === 'stats' || command.name === 'inlinestats') {
messages.push(errors.unknownAggFunction(arg));
@ -290,54 +280,36 @@ function validateCommand(
function validateOption(
option: ESQLCommandOption,
optionDef: CommandOptionsDefinition | undefined,
command: ESQLCommand,
referenceMaps: ReferenceMaps
): ESQLMessage[] {
// check if the arguments of the option are of the correct type
const messages: ESQLMessage[] = [];
if (option.incomplete || command.incomplete) {
if (option.incomplete || command.incomplete || option.name === 'metadata') {
return messages;
}
if (!optionDef) {
messages.push(
getMessageFromId({
messageId: 'unknownOption',
values: { command: command.name.toUpperCase(), option: option.name },
locations: option.location,
})
);
if (option.name === 'metadata') {
// Validation for the metadata statement is handled in the FROM command's validate method
return messages;
}
// use dedicate validate fn if provided
if (optionDef.validate) {
const fields = METADATA_FIELDS;
messages.push(...optionDef.validate(option, command, new Set(fields)));
}
if (!optionDef.skipCommonValidation) {
option.args.forEach((arg) => {
if (!Array.isArray(arg)) {
if (!optionDef.signature.multipleParams) {
if (isColumnItem(arg)) {
messages.push(...validateColumnForCommand(arg, command.name, referenceMaps));
}
} else {
if (isColumnItem(arg)) {
messages.push(...validateColumnForCommand(arg, command.name, referenceMaps));
}
if (isFunctionItem(arg)) {
messages.push(
...validateFunction({
fn: arg,
parentCommand: command.name,
parentOption: option.name,
references: referenceMaps,
})
);
}
}
}
});
for (const arg of option.args) {
if (Array.isArray(arg)) {
continue;
}
if (isColumnItem(arg)) {
messages.push(...validateColumnForCommand(arg, command.name, referenceMaps));
} else if (isFunctionItem(arg)) {
messages.push(
...validateFunction({
fn: arg,
parentCommand: command.name,
parentOption: option.name,
references: referenceMaps,
})
);
}
}
return messages;

View file

@ -5646,7 +5646,6 @@
"kbn-esql-validation-autocomplete.esql.definitions.atan2": "L'angle entre l'axe positif des x et le rayon allant de l'origine au point (x , y) dans le plan cartésien, exprimé en radians.",
"kbn-esql-validation-autocomplete.esql.definitions.avg": "La moyenne d'un champ numérique.",
"kbn-esql-validation-autocomplete.esql.definitions.bit_length": "Renvoie la longueur d'une chaîne en bits.",
"kbn-esql-validation-autocomplete.esql.definitions.byDoc": "Par",
"kbn-esql-validation-autocomplete.esql.definitions.byte_length": "Renvoie la longueur d'une chaîne en octets.",
"kbn-esql-validation-autocomplete.esql.definitions.case": "Accepte les paires de conditions et de valeurs. La fonction renvoie la valeur correspondant à la première condition évaluée à `true` (vraie). Si le nombre d'arguments est impair, le dernier argument est la valeur par défaut qui est renvoyée si aucune condition ne correspond.",
"kbn-esql-validation-autocomplete.esql.definitions.categorize": "Regroupe les messages textuels en catégories de valeurs textuelles au format similaire.",
@ -5855,7 +5854,6 @@
"kbn-esql-validation-autocomplete.esql.validation.unknownColumnType": "Type inconnu",
"kbn-esql-validation-autocomplete.esql.validation.unknownIndex": "Index inconnu [{name}]",
"kbn-esql-validation-autocomplete.esql.validation.unknownInterval": "Qualificatif d'intervalle de temps inattendu : \"{value}\"",
"kbn-esql-validation-autocomplete.esql.validation.unknownOption": "Option non valide pour {command} : [{option}]",
"kbn-esql-validation-autocomplete.esql.validation.unknownPolicy": "Politique [{name}] inconnue",
"kbn-esql-validation-autocomplete.esql.validation.unsupportedColumnTypeForCommand": "{command} ne prend en charge que les valeurs {type} {typeCount, plural, one {type} other {types}}, [{column}] de type [{givenType}] trouvé",
"kbn-esql-validation-autocomplete.esql.validation.unsupportedFieldType": "Le champ [{field}] ne peut pas être récupéré, il n'est pas pris en charge ou n'est pas indexé ; renvoi de valeur null",

View file

@ -5641,7 +5641,6 @@
"kbn-esql-validation-autocomplete.esql.definitions.atan2": "直交平面上の原点から点x , yに向かう光線と正のx軸のなす角ラジアン表記。",
"kbn-esql-validation-autocomplete.esql.definitions.avg": "数値フィールドの平均。",
"kbn-esql-validation-autocomplete.esql.definitions.bit_length": "文字列のビット長を返します。",
"kbn-esql-validation-autocomplete.esql.definitions.byDoc": "グループ基準",
"kbn-esql-validation-autocomplete.esql.definitions.byte_length": "文字列のバイト長を返します。",
"kbn-esql-validation-autocomplete.esql.definitions.case": "条件と値のペアを指定できます。この関数は、最初にtrueと評価された条件に属する値を返します。引数の数が奇数の場合、最後の引数は条件に一致しない場合に返されるデフォルト値になります。",
"kbn-esql-validation-autocomplete.esql.definitions.categorize": "テキストメッセージを、同様の書式のテキスト値のカテゴリに分類します。",
@ -5851,7 +5850,6 @@
"kbn-esql-validation-autocomplete.esql.validation.unknownColumnType": "不明なタイプ",
"kbn-esql-validation-autocomplete.esql.validation.unknownIndex": "不明なインデックス[{name}]",
"kbn-esql-validation-autocomplete.esql.validation.unknownInterval": "想定されていない時間間隔修飾子:''{value}''",
"kbn-esql-validation-autocomplete.esql.validation.unknownOption": "{command}の無効なオプション:[{option}]",
"kbn-esql-validation-autocomplete.esql.validation.unknownPolicy": "不明なポリシー[{name}]",
"kbn-esql-validation-autocomplete.esql.validation.unsupportedColumnTypeForCommand": "{command}は{type} {typeCount, plural, other {型}}の値のみをサポートしていますが、[{givenType}]型の[{column}]が見つかりました",
"kbn-esql-validation-autocomplete.esql.validation.unsupportedFieldType": "フィールド[{field}]を取得できません。サポートされていないか、インデックス化されていません。NULLが返されます",

View file

@ -5651,7 +5651,6 @@
"kbn-esql-validation-autocomplete.esql.definitions.atan2": "笛卡儿平面中正 x 轴与从原点到点 (x , y) 构成的射线之间的角度,以弧度表示。",
"kbn-esql-validation-autocomplete.esql.definitions.avg": "数字字段的平均值。",
"kbn-esql-validation-autocomplete.esql.definitions.bit_length": "返回字符串的位长。",
"kbn-esql-validation-autocomplete.esql.definitions.byDoc": "依据",
"kbn-esql-validation-autocomplete.esql.definitions.byte_length": "返回字符串的字节长度。",
"kbn-esql-validation-autocomplete.esql.definitions.case": "接受成对的条件和值。此函数返回属于第一个评估为 `true` 的条件的值。如果参数数量为奇数,则最后一个参数为在无条件匹配时返回的默认值。",
"kbn-esql-validation-autocomplete.esql.definitions.categorize": "将文本消息分组为格式类似的文本值类别。",
@ -5861,7 +5860,6 @@
"kbn-esql-validation-autocomplete.esql.validation.unknownColumnType": "未知类型",
"kbn-esql-validation-autocomplete.esql.validation.unknownIndex": "未知索引 [{name}]",
"kbn-esql-validation-autocomplete.esql.validation.unknownInterval": "意外的时间间隔修饰词:“{value}”",
"kbn-esql-validation-autocomplete.esql.validation.unknownOption": "{command} 的选项无效:[{option}]",
"kbn-esql-validation-autocomplete.esql.validation.unknownPolicy": "未知策略 [{name}]",
"kbn-esql-validation-autocomplete.esql.validation.unsupportedColumnTypeForCommand": "{command} 只支持 {type} 种{typeCount, plural, other {类型}}的值,找到了 [{givenType}] 类型的 [{column}]",
"kbn-esql-validation-autocomplete.esql.validation.unsupportedFieldType": "无法检索字段 [{field}],它不受支持或未进行索引;正返回 null",