mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[ES|QL] New sync with ES changes (#176283)
## Summary Sync with https://github.com/elastic/elasticsearch/pull/104958 for support of builtin fn in STATS * validation ✅ * autocomplete ✅ * also fixed `STATS BY <field>` syntax  Sync with https://github.com/elastic/elasticsearch/pull/104913 for new `log` function * validation ✅ - also warning for negative values * autocomplete ✅  Sync with https://github.com/elastic/elasticsearch/pull/105064 for removal of `PROJECT` command * validation ✅ (both new and legacy syntax supported) * autocomplete ✅ (will only suggest new syntax)  Sync with https://github.com/elastic/elasticsearch/pull/105221 for removal of mandatory brackets for `METADATA` command option * validation ✅ (added warning deprecation message when using brackets) * autocomplete ✅  Sync with https://github.com/elastic/elasticsearch/pull/105224 for change of syntax for ENRICH ccq mode * validation ✅ * autocomplete ✅ (not directly promoted, the user has to type `_` to trigger it) * hover ✅ * code actions ✅   Do not merge until those 5 get merged. Additional things in this PR: * Added more tests for `callbacks` not passed scenario * covered more cases like those with `dissect` * Added more tests for signature params number (calling a function with an extra arg should return an error) * Cleaned up some more unused code * Improved messages on too many arguments for functions ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
This commit is contained in:
parent
8d43c0e718
commit
7bbea56c16
34 changed files with 2758 additions and 2441 deletions
|
@ -19,7 +19,6 @@ INLINESTATS : I N L I N E S T A T S -> pushMode(EXPRESSION_MODE);
|
|||
KEEP : K E E P -> pushMode(PROJECT_MODE);
|
||||
LIMIT : L I M I T -> pushMode(EXPRESSION_MODE);
|
||||
MV_EXPAND : M V UNDERSCORE E X P A N D -> pushMode(MVEXPAND_MODE);
|
||||
PROJECT : P R O J E C T -> pushMode(PROJECT_MODE);
|
||||
RENAME : R E N A M E -> pushMode(RENAME_MODE);
|
||||
ROW : R O W -> pushMode(EXPRESSION_MODE);
|
||||
SHOW : S H O W -> pushMode(SHOW_MODE);
|
||||
|
@ -218,7 +217,7 @@ FROM_WS
|
|||
: WS -> channel(HIDDEN)
|
||||
;
|
||||
//
|
||||
// DROP, KEEP, PROJECT
|
||||
// DROP, KEEP
|
||||
//
|
||||
mode PROJECT_MODE;
|
||||
PROJECT_PIPE : PIPE -> type(PIPE), popMode;
|
||||
|
@ -299,7 +298,8 @@ fragment ENRICH_POLICY_NAME_BODY
|
|||
: ~[\\/?"<>| ,#\t\r\n:]
|
||||
;
|
||||
ENRICH_POLICY_NAME
|
||||
: (LETTER | DIGIT) ENRICH_POLICY_NAME_BODY*
|
||||
// allow prefix for the policy to specify its resolution
|
||||
: (ENRICH_POLICY_NAME_BODY+ COLON)? ENRICH_POLICY_NAME_BODY+
|
||||
;
|
||||
|
||||
ENRICH_QUOTED_IDENTIFIER
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -9,118 +9,117 @@ INLINESTATS=8
|
|||
KEEP=9
|
||||
LIMIT=10
|
||||
MV_EXPAND=11
|
||||
PROJECT=12
|
||||
RENAME=13
|
||||
ROW=14
|
||||
SHOW=15
|
||||
SORT=16
|
||||
STATS=17
|
||||
WHERE=18
|
||||
UNKNOWN_CMD=19
|
||||
LINE_COMMENT=20
|
||||
MULTILINE_COMMENT=21
|
||||
WS=22
|
||||
EXPLAIN_WS=23
|
||||
EXPLAIN_LINE_COMMENT=24
|
||||
EXPLAIN_MULTILINE_COMMENT=25
|
||||
PIPE=26
|
||||
STRING=27
|
||||
INTEGER_LITERAL=28
|
||||
DECIMAL_LITERAL=29
|
||||
BY=30
|
||||
AND=31
|
||||
ASC=32
|
||||
ASSIGN=33
|
||||
COMMA=34
|
||||
DESC=35
|
||||
DOT=36
|
||||
FALSE=37
|
||||
FIRST=38
|
||||
LAST=39
|
||||
LP=40
|
||||
IN=41
|
||||
IS=42
|
||||
LIKE=43
|
||||
NOT=44
|
||||
NULL=45
|
||||
NULLS=46
|
||||
OR=47
|
||||
PARAM=48
|
||||
RLIKE=49
|
||||
RP=50
|
||||
TRUE=51
|
||||
EQ=52
|
||||
CIEQ=53
|
||||
NEQ=54
|
||||
LT=55
|
||||
LTE=56
|
||||
GT=57
|
||||
GTE=58
|
||||
PLUS=59
|
||||
MINUS=60
|
||||
ASTERISK=61
|
||||
SLASH=62
|
||||
PERCENT=63
|
||||
OPENING_BRACKET=64
|
||||
CLOSING_BRACKET=65
|
||||
UNQUOTED_IDENTIFIER=66
|
||||
QUOTED_IDENTIFIER=67
|
||||
EXPR_LINE_COMMENT=68
|
||||
EXPR_MULTILINE_COMMENT=69
|
||||
EXPR_WS=70
|
||||
METADATA=71
|
||||
FROM_UNQUOTED_IDENTIFIER=72
|
||||
FROM_LINE_COMMENT=73
|
||||
FROM_MULTILINE_COMMENT=74
|
||||
FROM_WS=75
|
||||
UNQUOTED_ID_PATTERN=76
|
||||
PROJECT_LINE_COMMENT=77
|
||||
PROJECT_MULTILINE_COMMENT=78
|
||||
PROJECT_WS=79
|
||||
AS=80
|
||||
RENAME_LINE_COMMENT=81
|
||||
RENAME_MULTILINE_COMMENT=82
|
||||
RENAME_WS=83
|
||||
ON=84
|
||||
WITH=85
|
||||
ENRICH_POLICY_NAME=86
|
||||
ENRICH_LINE_COMMENT=87
|
||||
ENRICH_MULTILINE_COMMENT=88
|
||||
ENRICH_WS=89
|
||||
ENRICH_FIELD_LINE_COMMENT=90
|
||||
ENRICH_FIELD_MULTILINE_COMMENT=91
|
||||
ENRICH_FIELD_WS=92
|
||||
MVEXPAND_LINE_COMMENT=93
|
||||
MVEXPAND_MULTILINE_COMMENT=94
|
||||
MVEXPAND_WS=95
|
||||
INFO=96
|
||||
FUNCTIONS=97
|
||||
SHOW_LINE_COMMENT=98
|
||||
SHOW_MULTILINE_COMMENT=99
|
||||
SHOW_WS=100
|
||||
COLON=101
|
||||
SETTING=102
|
||||
SETTING_LINE_COMMENT=103
|
||||
SETTTING_MULTILINE_COMMENT=104
|
||||
SETTING_WS=105
|
||||
'|'=26
|
||||
'='=33
|
||||
','=34
|
||||
'.'=36
|
||||
'('=40
|
||||
'?'=48
|
||||
')'=50
|
||||
'=='=52
|
||||
'=~'=53
|
||||
'!='=54
|
||||
'<'=55
|
||||
'<='=56
|
||||
'>'=57
|
||||
'>='=58
|
||||
'+'=59
|
||||
'-'=60
|
||||
'*'=61
|
||||
'/'=62
|
||||
'%'=63
|
||||
']'=65
|
||||
':'=101
|
||||
RENAME=12
|
||||
ROW=13
|
||||
SHOW=14
|
||||
SORT=15
|
||||
STATS=16
|
||||
WHERE=17
|
||||
UNKNOWN_CMD=18
|
||||
LINE_COMMENT=19
|
||||
MULTILINE_COMMENT=20
|
||||
WS=21
|
||||
EXPLAIN_WS=22
|
||||
EXPLAIN_LINE_COMMENT=23
|
||||
EXPLAIN_MULTILINE_COMMENT=24
|
||||
PIPE=25
|
||||
STRING=26
|
||||
INTEGER_LITERAL=27
|
||||
DECIMAL_LITERAL=28
|
||||
BY=29
|
||||
AND=30
|
||||
ASC=31
|
||||
ASSIGN=32
|
||||
COMMA=33
|
||||
DESC=34
|
||||
DOT=35
|
||||
FALSE=36
|
||||
FIRST=37
|
||||
LAST=38
|
||||
LP=39
|
||||
IN=40
|
||||
IS=41
|
||||
LIKE=42
|
||||
NOT=43
|
||||
NULL=44
|
||||
NULLS=45
|
||||
OR=46
|
||||
PARAM=47
|
||||
RLIKE=48
|
||||
RP=49
|
||||
TRUE=50
|
||||
EQ=51
|
||||
CIEQ=52
|
||||
NEQ=53
|
||||
LT=54
|
||||
LTE=55
|
||||
GT=56
|
||||
GTE=57
|
||||
PLUS=58
|
||||
MINUS=59
|
||||
ASTERISK=60
|
||||
SLASH=61
|
||||
PERCENT=62
|
||||
OPENING_BRACKET=63
|
||||
CLOSING_BRACKET=64
|
||||
UNQUOTED_IDENTIFIER=65
|
||||
QUOTED_IDENTIFIER=66
|
||||
EXPR_LINE_COMMENT=67
|
||||
EXPR_MULTILINE_COMMENT=68
|
||||
EXPR_WS=69
|
||||
METADATA=70
|
||||
FROM_UNQUOTED_IDENTIFIER=71
|
||||
FROM_LINE_COMMENT=72
|
||||
FROM_MULTILINE_COMMENT=73
|
||||
FROM_WS=74
|
||||
UNQUOTED_ID_PATTERN=75
|
||||
PROJECT_LINE_COMMENT=76
|
||||
PROJECT_MULTILINE_COMMENT=77
|
||||
PROJECT_WS=78
|
||||
AS=79
|
||||
RENAME_LINE_COMMENT=80
|
||||
RENAME_MULTILINE_COMMENT=81
|
||||
RENAME_WS=82
|
||||
ON=83
|
||||
WITH=84
|
||||
ENRICH_POLICY_NAME=85
|
||||
ENRICH_LINE_COMMENT=86
|
||||
ENRICH_MULTILINE_COMMENT=87
|
||||
ENRICH_WS=88
|
||||
ENRICH_FIELD_LINE_COMMENT=89
|
||||
ENRICH_FIELD_MULTILINE_COMMENT=90
|
||||
ENRICH_FIELD_WS=91
|
||||
MVEXPAND_LINE_COMMENT=92
|
||||
MVEXPAND_MULTILINE_COMMENT=93
|
||||
MVEXPAND_WS=94
|
||||
INFO=95
|
||||
FUNCTIONS=96
|
||||
SHOW_LINE_COMMENT=97
|
||||
SHOW_MULTILINE_COMMENT=98
|
||||
SHOW_WS=99
|
||||
COLON=100
|
||||
SETTING=101
|
||||
SETTING_LINE_COMMENT=102
|
||||
SETTTING_MULTILINE_COMMENT=103
|
||||
SETTING_WS=104
|
||||
'|'=25
|
||||
'='=32
|
||||
','=33
|
||||
'.'=35
|
||||
'('=39
|
||||
'?'=47
|
||||
')'=49
|
||||
'=='=51
|
||||
'=~'=52
|
||||
'!='=53
|
||||
'<'=54
|
||||
'<='=55
|
||||
'>'=56
|
||||
'>='=57
|
||||
'+'=58
|
||||
'-'=59
|
||||
'*'=60
|
||||
'/'=61
|
||||
'%'=62
|
||||
']'=64
|
||||
':'=100
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -102,7 +102,16 @@ fromCommand
|
|||
;
|
||||
|
||||
metadata
|
||||
: OPENING_BRACKET METADATA fromIdentifier (COMMA fromIdentifier)* CLOSING_BRACKET
|
||||
: metadataOption
|
||||
| deprecated_metadata
|
||||
;
|
||||
|
||||
metadataOption
|
||||
: METADATA fromIdentifier (COMMA fromIdentifier)*
|
||||
;
|
||||
|
||||
deprecated_metadata
|
||||
: OPENING_BRACKET metadataOption CLOSING_BRACKET
|
||||
;
|
||||
|
||||
|
||||
|
@ -168,7 +177,6 @@ orderExpression
|
|||
|
||||
keepCommand
|
||||
: KEEP qualifiedNamePattern (COMMA qualifiedNamePattern)*
|
||||
| PROJECT qualifiedNamePattern (COMMA qualifiedNamePattern)*
|
||||
;
|
||||
|
||||
dropCommand
|
||||
|
@ -242,13 +250,9 @@ showCommand
|
|||
;
|
||||
|
||||
enrichCommand
|
||||
: ENRICH setting* policyName=ENRICH_POLICY_NAME (ON matchField=qualifiedNamePattern)? (WITH enrichWithClause (COMMA enrichWithClause)*)?
|
||||
: ENRICH policyName=ENRICH_POLICY_NAME (ON matchField=qualifiedNamePattern)? (WITH enrichWithClause (COMMA enrichWithClause)*)?
|
||||
;
|
||||
|
||||
enrichWithClause
|
||||
: (newName=qualifiedNamePattern ASSIGN)? enrichField=qualifiedNamePattern
|
||||
;
|
||||
|
||||
setting
|
||||
: OPENING_BRACKET name=SETTING COLON value=SETTING CLOSING_BRACKET
|
||||
;
|
File diff suppressed because one or more lines are too long
|
@ -9,118 +9,117 @@ INLINESTATS=8
|
|||
KEEP=9
|
||||
LIMIT=10
|
||||
MV_EXPAND=11
|
||||
PROJECT=12
|
||||
RENAME=13
|
||||
ROW=14
|
||||
SHOW=15
|
||||
SORT=16
|
||||
STATS=17
|
||||
WHERE=18
|
||||
UNKNOWN_CMD=19
|
||||
LINE_COMMENT=20
|
||||
MULTILINE_COMMENT=21
|
||||
WS=22
|
||||
EXPLAIN_WS=23
|
||||
EXPLAIN_LINE_COMMENT=24
|
||||
EXPLAIN_MULTILINE_COMMENT=25
|
||||
PIPE=26
|
||||
STRING=27
|
||||
INTEGER_LITERAL=28
|
||||
DECIMAL_LITERAL=29
|
||||
BY=30
|
||||
AND=31
|
||||
ASC=32
|
||||
ASSIGN=33
|
||||
COMMA=34
|
||||
DESC=35
|
||||
DOT=36
|
||||
FALSE=37
|
||||
FIRST=38
|
||||
LAST=39
|
||||
LP=40
|
||||
IN=41
|
||||
IS=42
|
||||
LIKE=43
|
||||
NOT=44
|
||||
NULL=45
|
||||
NULLS=46
|
||||
OR=47
|
||||
PARAM=48
|
||||
RLIKE=49
|
||||
RP=50
|
||||
TRUE=51
|
||||
EQ=52
|
||||
CIEQ=53
|
||||
NEQ=54
|
||||
LT=55
|
||||
LTE=56
|
||||
GT=57
|
||||
GTE=58
|
||||
PLUS=59
|
||||
MINUS=60
|
||||
ASTERISK=61
|
||||
SLASH=62
|
||||
PERCENT=63
|
||||
OPENING_BRACKET=64
|
||||
CLOSING_BRACKET=65
|
||||
UNQUOTED_IDENTIFIER=66
|
||||
QUOTED_IDENTIFIER=67
|
||||
EXPR_LINE_COMMENT=68
|
||||
EXPR_MULTILINE_COMMENT=69
|
||||
EXPR_WS=70
|
||||
METADATA=71
|
||||
FROM_UNQUOTED_IDENTIFIER=72
|
||||
FROM_LINE_COMMENT=73
|
||||
FROM_MULTILINE_COMMENT=74
|
||||
FROM_WS=75
|
||||
UNQUOTED_ID_PATTERN=76
|
||||
PROJECT_LINE_COMMENT=77
|
||||
PROJECT_MULTILINE_COMMENT=78
|
||||
PROJECT_WS=79
|
||||
AS=80
|
||||
RENAME_LINE_COMMENT=81
|
||||
RENAME_MULTILINE_COMMENT=82
|
||||
RENAME_WS=83
|
||||
ON=84
|
||||
WITH=85
|
||||
ENRICH_POLICY_NAME=86
|
||||
ENRICH_LINE_COMMENT=87
|
||||
ENRICH_MULTILINE_COMMENT=88
|
||||
ENRICH_WS=89
|
||||
ENRICH_FIELD_LINE_COMMENT=90
|
||||
ENRICH_FIELD_MULTILINE_COMMENT=91
|
||||
ENRICH_FIELD_WS=92
|
||||
MVEXPAND_LINE_COMMENT=93
|
||||
MVEXPAND_MULTILINE_COMMENT=94
|
||||
MVEXPAND_WS=95
|
||||
INFO=96
|
||||
FUNCTIONS=97
|
||||
SHOW_LINE_COMMENT=98
|
||||
SHOW_MULTILINE_COMMENT=99
|
||||
SHOW_WS=100
|
||||
COLON=101
|
||||
SETTING=102
|
||||
SETTING_LINE_COMMENT=103
|
||||
SETTTING_MULTILINE_COMMENT=104
|
||||
SETTING_WS=105
|
||||
'|'=26
|
||||
'='=33
|
||||
','=34
|
||||
'.'=36
|
||||
'('=40
|
||||
'?'=48
|
||||
')'=50
|
||||
'=='=52
|
||||
'=~'=53
|
||||
'!='=54
|
||||
'<'=55
|
||||
'<='=56
|
||||
'>'=57
|
||||
'>='=58
|
||||
'+'=59
|
||||
'-'=60
|
||||
'*'=61
|
||||
'/'=62
|
||||
'%'=63
|
||||
']'=65
|
||||
':'=101
|
||||
RENAME=12
|
||||
ROW=13
|
||||
SHOW=14
|
||||
SORT=15
|
||||
STATS=16
|
||||
WHERE=17
|
||||
UNKNOWN_CMD=18
|
||||
LINE_COMMENT=19
|
||||
MULTILINE_COMMENT=20
|
||||
WS=21
|
||||
EXPLAIN_WS=22
|
||||
EXPLAIN_LINE_COMMENT=23
|
||||
EXPLAIN_MULTILINE_COMMENT=24
|
||||
PIPE=25
|
||||
STRING=26
|
||||
INTEGER_LITERAL=27
|
||||
DECIMAL_LITERAL=28
|
||||
BY=29
|
||||
AND=30
|
||||
ASC=31
|
||||
ASSIGN=32
|
||||
COMMA=33
|
||||
DESC=34
|
||||
DOT=35
|
||||
FALSE=36
|
||||
FIRST=37
|
||||
LAST=38
|
||||
LP=39
|
||||
IN=40
|
||||
IS=41
|
||||
LIKE=42
|
||||
NOT=43
|
||||
NULL=44
|
||||
NULLS=45
|
||||
OR=46
|
||||
PARAM=47
|
||||
RLIKE=48
|
||||
RP=49
|
||||
TRUE=50
|
||||
EQ=51
|
||||
CIEQ=52
|
||||
NEQ=53
|
||||
LT=54
|
||||
LTE=55
|
||||
GT=56
|
||||
GTE=57
|
||||
PLUS=58
|
||||
MINUS=59
|
||||
ASTERISK=60
|
||||
SLASH=61
|
||||
PERCENT=62
|
||||
OPENING_BRACKET=63
|
||||
CLOSING_BRACKET=64
|
||||
UNQUOTED_IDENTIFIER=65
|
||||
QUOTED_IDENTIFIER=66
|
||||
EXPR_LINE_COMMENT=67
|
||||
EXPR_MULTILINE_COMMENT=68
|
||||
EXPR_WS=69
|
||||
METADATA=70
|
||||
FROM_UNQUOTED_IDENTIFIER=71
|
||||
FROM_LINE_COMMENT=72
|
||||
FROM_MULTILINE_COMMENT=73
|
||||
FROM_WS=74
|
||||
UNQUOTED_ID_PATTERN=75
|
||||
PROJECT_LINE_COMMENT=76
|
||||
PROJECT_MULTILINE_COMMENT=77
|
||||
PROJECT_WS=78
|
||||
AS=79
|
||||
RENAME_LINE_COMMENT=80
|
||||
RENAME_MULTILINE_COMMENT=81
|
||||
RENAME_WS=82
|
||||
ON=83
|
||||
WITH=84
|
||||
ENRICH_POLICY_NAME=85
|
||||
ENRICH_LINE_COMMENT=86
|
||||
ENRICH_MULTILINE_COMMENT=87
|
||||
ENRICH_WS=88
|
||||
ENRICH_FIELD_LINE_COMMENT=89
|
||||
ENRICH_FIELD_MULTILINE_COMMENT=90
|
||||
ENRICH_FIELD_WS=91
|
||||
MVEXPAND_LINE_COMMENT=92
|
||||
MVEXPAND_MULTILINE_COMMENT=93
|
||||
MVEXPAND_WS=94
|
||||
INFO=95
|
||||
FUNCTIONS=96
|
||||
SHOW_LINE_COMMENT=97
|
||||
SHOW_MULTILINE_COMMENT=98
|
||||
SHOW_WS=99
|
||||
COLON=100
|
||||
SETTING=101
|
||||
SETTING_LINE_COMMENT=102
|
||||
SETTTING_MULTILINE_COMMENT=103
|
||||
SETTING_WS=104
|
||||
'|'=25
|
||||
'='=32
|
||||
','=33
|
||||
'.'=35
|
||||
'('=39
|
||||
'?'=47
|
||||
')'=49
|
||||
'=='=51
|
||||
'=~'=52
|
||||
'!='=53
|
||||
'<'=54
|
||||
'<='=55
|
||||
'>'=56
|
||||
'>='=57
|
||||
'+'=58
|
||||
'-'=59
|
||||
'*'=60
|
||||
'/'=61
|
||||
'%'=62
|
||||
']'=64
|
||||
':'=100
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -49,6 +49,8 @@ import { FieldsContext } from "./esql_parser";
|
|||
import { FieldContext } from "./esql_parser";
|
||||
import { FromCommandContext } from "./esql_parser";
|
||||
import { MetadataContext } from "./esql_parser";
|
||||
import { MetadataOptionContext } from "./esql_parser";
|
||||
import { Deprecated_metadataContext } from "./esql_parser";
|
||||
import { EvalCommandContext } from "./esql_parser";
|
||||
import { StatsCommandContext } from "./esql_parser";
|
||||
import { InlinestatsCommandContext } from "./esql_parser";
|
||||
|
@ -81,7 +83,6 @@ import { SubqueryExpressionContext } from "./esql_parser";
|
|||
import { ShowCommandContext } from "./esql_parser";
|
||||
import { EnrichCommandContext } from "./esql_parser";
|
||||
import { EnrichWithClauseContext } from "./esql_parser";
|
||||
import { SettingContext } from "./esql_parser";
|
||||
|
||||
|
||||
/**
|
||||
|
@ -642,6 +643,28 @@ export interface esql_parserListener extends ParseTreeListener {
|
|||
*/
|
||||
exitMetadata?: (ctx: MetadataContext) => void;
|
||||
|
||||
/**
|
||||
* Enter a parse tree produced by `esql_parser.metadataOption`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
enterMetadataOption?: (ctx: MetadataOptionContext) => void;
|
||||
/**
|
||||
* Exit a parse tree produced by `esql_parser.metadataOption`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
exitMetadataOption?: (ctx: MetadataOptionContext) => void;
|
||||
|
||||
/**
|
||||
* Enter a parse tree produced by `esql_parser.deprecated_metadata`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
enterDeprecated_metadata?: (ctx: Deprecated_metadataContext) => void;
|
||||
/**
|
||||
* Exit a parse tree produced by `esql_parser.deprecated_metadata`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
exitDeprecated_metadata?: (ctx: Deprecated_metadataContext) => void;
|
||||
|
||||
/**
|
||||
* Enter a parse tree produced by `esql_parser.evalCommand`.
|
||||
* @param ctx the parse tree
|
||||
|
@ -993,16 +1016,5 @@ export interface esql_parserListener extends ParseTreeListener {
|
|||
* @param ctx the parse tree
|
||||
*/
|
||||
exitEnrichWithClause?: (ctx: EnrichWithClauseContext) => void;
|
||||
|
||||
/**
|
||||
* Enter a parse tree produced by `esql_parser.setting`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
enterSetting?: (ctx: SettingContext) => void;
|
||||
/**
|
||||
* Exit a parse tree produced by `esql_parser.setting`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
exitSetting?: (ctx: SettingContext) => void;
|
||||
}
|
||||
|
||||
|
|
|
@ -43,7 +43,6 @@ import {
|
|||
getPolicyName,
|
||||
getMatchField,
|
||||
getEnrichClauses,
|
||||
getPolicySettings,
|
||||
} from './ast_walker';
|
||||
import type { ESQLAst } from './types';
|
||||
|
||||
|
@ -117,10 +116,12 @@ export class AstListener implements ESQLParserListener {
|
|||
this.ast.push(commandAst);
|
||||
commandAst.args.push(...collectAllSourceIdentifiers(ctx));
|
||||
const metadataContext = ctx.metadata();
|
||||
if (metadataContext) {
|
||||
const option = createOption(metadataContext.METADATA().text.toLowerCase(), metadataContext);
|
||||
const metadataContent =
|
||||
metadataContext?.deprecated_metadata()?.metadataOption() || metadataContext?.metadataOption();
|
||||
if (metadataContent) {
|
||||
const option = createOption(metadataContent.METADATA().text.toLowerCase(), metadataContent);
|
||||
commandAst.args.push(option);
|
||||
option.args.push(...collectAllColumnIdentifiers(metadataContext));
|
||||
option.args.push(...collectAllColumnIdentifiers(metadataContent));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -251,11 +252,6 @@ export class AstListener implements ESQLParserListener {
|
|||
exitEnrichCommand(ctx: EnrichCommandContext) {
|
||||
const command = createCommand('enrich', ctx);
|
||||
this.ast.push(command);
|
||||
command.args.push(
|
||||
...getPolicySettings(ctx),
|
||||
...getPolicyName(ctx),
|
||||
...getMatchField(ctx),
|
||||
...getEnrichClauses(ctx)
|
||||
);
|
||||
command.args.push(...getPolicyName(ctx), ...getMatchField(ctx), ...getEnrichClauses(ctx));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ import type {
|
|||
DecimalValueContext,
|
||||
IntegerValueContext,
|
||||
QualifiedIntegerLiteralContext,
|
||||
SettingContext,
|
||||
} from '../../antlr/esql_parser';
|
||||
import { getPosition } from './ast_position_utils';
|
||||
import type {
|
||||
|
@ -198,15 +197,15 @@ export function computeLocationExtends(fn: ESQLFunction) {
|
|||
|
||||
/* SCRIPT_MARKER_START */
|
||||
function getQuotedText(ctx: ParserRuleContext) {
|
||||
return [67 /* esql_parser.QUOTED_IDENTIFIER */]
|
||||
return [66 /* esql_parser.QUOTED_IDENTIFIER */]
|
||||
.map((keyCode) => ctx.tryGetToken(keyCode, 0))
|
||||
.filter(nonNullable)[0];
|
||||
}
|
||||
|
||||
function getUnquotedText(ctx: ParserRuleContext) {
|
||||
return [
|
||||
66 /* esql_parser.UNQUOTED_IDENTIFIER */, 72 /* esql_parser.FROM_UNQUOTED_IDENTIFIER */,
|
||||
76 /* esql_parser.UNQUOTED_ID_PATTERN */,
|
||||
65 /* esql_parser.UNQUOTED_IDENTIFIER */, 71 /* esql_parser.FROM_UNQUOTED_IDENTIFIER */,
|
||||
75 /* esql_parser.UNQUOTED_ID_PATTERN */,
|
||||
]
|
||||
.map((keyCode) => ctx.tryGetToken(keyCode, 0))
|
||||
.filter(nonNullable)[0];
|
||||
|
@ -231,16 +230,13 @@ export function wrapIdentifierAsArray<T extends ParserRuleContext>(identifierCtx
|
|||
return Array.isArray(identifierCtx) ? identifierCtx : [identifierCtx];
|
||||
}
|
||||
|
||||
export function createSettingTuple(ctx: SettingContext): ESQLCommandMode {
|
||||
export function createSetting(policyName: Token, mode: string): ESQLCommandMode {
|
||||
return {
|
||||
type: 'mode',
|
||||
name: ctx._name?.text || '',
|
||||
text: ctx.text!,
|
||||
location: getPosition(ctx.start, ctx.stop),
|
||||
incomplete:
|
||||
(ctx._name?.text ? isMissingText(ctx._name.text) : true) ||
|
||||
(ctx._value?.text ? isMissingText(ctx._value.text) : true),
|
||||
args: [],
|
||||
name: mode.replace('_', '').toLowerCase(),
|
||||
text: mode,
|
||||
location: getPosition(policyName, { stopIndex: policyName.startIndex + mode.length - 1 }), // unfortunately this is the only location we have
|
||||
incomplete: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -248,13 +244,16 @@ export function createSettingTuple(ctx: SettingContext): ESQLCommandMode {
|
|||
* In https://github.com/elastic/elasticsearch/pull/103949 the ENRICH policy name
|
||||
* changed from rule to token type so we need to handle this specifically
|
||||
*/
|
||||
export function createPolicy(token: Token): ESQLSource {
|
||||
export function createPolicy(token: Token, policy: string): ESQLSource {
|
||||
return {
|
||||
type: 'source',
|
||||
name: token.text!,
|
||||
text: token.text!,
|
||||
name: policy,
|
||||
text: policy,
|
||||
sourceType: 'policy',
|
||||
location: getPosition(token),
|
||||
location: getPosition({
|
||||
startIndex: token.stopIndex - policy.length + 1,
|
||||
stopIndex: token.stopIndex,
|
||||
}), // take into account ccq modes
|
||||
incomplete: false,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -8,7 +8,10 @@
|
|||
|
||||
import type { Token } from 'antlr4ts';
|
||||
|
||||
export function getPosition(token: Token | undefined, lastToken?: Token | undefined) {
|
||||
export function getPosition(
|
||||
token: Pick<Token, 'startIndex' | 'stopIndex'> | undefined,
|
||||
lastToken?: Pick<Token, 'stopIndex'> | undefined
|
||||
) {
|
||||
if (!token || token.startIndex < 0) {
|
||||
return { min: 0, max: 0 };
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ import {
|
|||
LogicalBinaryContext,
|
||||
LogicalInContext,
|
||||
LogicalNotContext,
|
||||
MetadataContext,
|
||||
MetadataOptionContext,
|
||||
MvExpandCommandContext,
|
||||
NullLiteralContext,
|
||||
NumericArrayLiteralContext,
|
||||
|
@ -74,9 +74,8 @@ import {
|
|||
createColumnStar,
|
||||
wrapIdentifierAsArray,
|
||||
createPolicy,
|
||||
createSettingTuple,
|
||||
createLiteralString,
|
||||
isMissingText,
|
||||
createSetting,
|
||||
} from './ast_helpers';
|
||||
import { getPosition } from './ast_position_utils';
|
||||
import type {
|
||||
|
@ -92,9 +91,9 @@ export function collectAllSourceIdentifiers(ctx: FromCommandContext): ESQLAstIte
|
|||
}
|
||||
|
||||
function extractIdentifiers(
|
||||
ctx: KeepCommandContext | DropCommandContext | MvExpandCommandContext | MetadataContext
|
||||
ctx: KeepCommandContext | DropCommandContext | MvExpandCommandContext | MetadataOptionContext
|
||||
) {
|
||||
if (ctx instanceof MetadataContext) {
|
||||
if (ctx instanceof MetadataOptionContext) {
|
||||
return wrapIdentifierAsArray(ctx.fromIdentifier());
|
||||
}
|
||||
if (ctx instanceof MvExpandCommandContext) {
|
||||
|
@ -114,32 +113,22 @@ function makeColumnsOutOfIdentifiers(identifiers: ParserRuleContext[]) {
|
|||
}
|
||||
|
||||
export function collectAllColumnIdentifiers(
|
||||
ctx: KeepCommandContext | DropCommandContext | MvExpandCommandContext | MetadataContext
|
||||
ctx: KeepCommandContext | DropCommandContext | MvExpandCommandContext | MetadataOptionContext
|
||||
): ESQLAstItem[] {
|
||||
const identifiers = extractIdentifiers(ctx);
|
||||
return makeColumnsOutOfIdentifiers(identifiers);
|
||||
}
|
||||
|
||||
export function getPolicyName(ctx: EnrichCommandContext) {
|
||||
if (!ctx._policyName || (ctx._policyName.text && /<missing /.test(ctx._policyName.text))) {
|
||||
if (!ctx._policyName || !ctx._policyName.text || /<missing /.test(ctx._policyName.text)) {
|
||||
return [];
|
||||
}
|
||||
return [createPolicy(ctx._policyName)];
|
||||
}
|
||||
|
||||
export function getPolicySettings(ctx: EnrichCommandContext) {
|
||||
if (!ctx.setting() || !ctx.setting().length) {
|
||||
return [];
|
||||
const policyComponents = ctx._policyName.text.split(':');
|
||||
if (policyComponents.length > 1) {
|
||||
const [setting, policyName] = policyComponents;
|
||||
return [createSetting(ctx._policyName, setting), createPolicy(ctx._policyName, policyName)];
|
||||
}
|
||||
return ctx.setting().map((setting) => {
|
||||
const node = createSettingTuple(setting);
|
||||
if (setting._name?.text && setting._value?.text) {
|
||||
node.args.push(createLiteralString(setting._value)!);
|
||||
return node;
|
||||
}
|
||||
// incomplete setting
|
||||
return node;
|
||||
});
|
||||
return [createPolicy(ctx._policyName, policyComponents[0])];
|
||||
}
|
||||
|
||||
export function getMatchField(ctx: EnrichCommandContext) {
|
||||
|
|
|
@ -18,6 +18,7 @@ import { statsAggregationFunctionDefinitions } from '../definitions/aggs';
|
|||
import { chronoLiterals, timeLiterals } from '../definitions/literals';
|
||||
import { commandDefinitions } from '../definitions/commands';
|
||||
import { TRIGGER_SUGGESTION_COMMAND } from './factories';
|
||||
import { camelCase } from 'lodash';
|
||||
|
||||
const triggerCharacters = [',', '(', '=', ' '];
|
||||
|
||||
|
@ -332,11 +333,13 @@ describe('autocomplete', () => {
|
|||
);
|
||||
testSuggestions('from ', suggestedIndexes);
|
||||
testSuggestions('from a,', suggestedIndexes);
|
||||
testSuggestions('from a, b ', ['[metadata $0 ]', '|', ',']);
|
||||
testSuggestions('from a, b ', ['metadata $0', '|', ',']);
|
||||
testSuggestions('from *,', suggestedIndexes);
|
||||
testSuggestions('from index', suggestedIndexes, 6 /* index index in from */);
|
||||
testSuggestions('from a, b [metadata ]', ['_index', '_score'], 20);
|
||||
testSuggestions('from a, b metadata ', ['_index', '_score'], 19);
|
||||
testSuggestions('from a, b [metadata _index, ]', ['_score'], 27);
|
||||
testSuggestions('from a, b metadata _index, ', ['_score'], 26);
|
||||
});
|
||||
|
||||
describe('show', () => {
|
||||
|
@ -542,11 +545,11 @@ describe('autocomplete', () => {
|
|||
|
||||
describe('rename', () => {
|
||||
testSuggestions('from a | rename ', getFieldNamesByType('any'));
|
||||
testSuggestions('from a | rename stringField ', ['as']);
|
||||
testSuggestions('from a | rename stringField ', ['as $0']);
|
||||
testSuggestions('from a | rename stringField as ', ['var0']);
|
||||
});
|
||||
|
||||
for (const command of ['keep', 'drop', 'project']) {
|
||||
for (const command of ['keep', 'drop']) {
|
||||
describe(command, () => {
|
||||
testSuggestions(`from a | ${command} `, getFieldNamesByType('any'));
|
||||
testSuggestions(
|
||||
|
@ -560,40 +563,52 @@ describe('autocomplete', () => {
|
|||
const allAggFunctions = getFunctionSignaturesByReturnType('stats', 'any', {
|
||||
agg: true,
|
||||
});
|
||||
testSuggestions('from a | stats ', ['var0 =', ...allAggFunctions]);
|
||||
const allEvaFunctions = getFunctionSignaturesByReturnType('stats', 'any', {
|
||||
evalMath: true,
|
||||
});
|
||||
testSuggestions('from a | stats ', ['var0 =', ...allAggFunctions, ...allEvaFunctions]);
|
||||
testSuggestions('from a | stats a ', ['= $0']);
|
||||
testSuggestions('from a | stats a=', [...allAggFunctions]);
|
||||
testSuggestions('from a | stats a=', [...allAggFunctions, ...allEvaFunctions]);
|
||||
testSuggestions('from a | stats a=max(b) by ', [
|
||||
...getFieldNamesByType('any'),
|
||||
...getFunctionSignaturesByReturnType('eval', 'any', { evalMath: true }),
|
||||
...allEvaFunctions,
|
||||
'var0 =',
|
||||
]);
|
||||
testSuggestions('from a | stats a=max(b) BY ', [
|
||||
...getFieldNamesByType('any'),
|
||||
...getFunctionSignaturesByReturnType('eval', 'any', { evalMath: true }),
|
||||
...allEvaFunctions,
|
||||
'var0 =',
|
||||
]);
|
||||
testSuggestions('from a | stats a=c by d ', ['|', ',']);
|
||||
testSuggestions('from a | stats a=c by d, ', [
|
||||
...getFieldNamesByType('any'),
|
||||
...getFunctionSignaturesByReturnType('eval', 'any', { evalMath: true }),
|
||||
...allEvaFunctions,
|
||||
'var0 =',
|
||||
]);
|
||||
testSuggestions('from a | stats a=max(b), ', ['var0 =', ...allAggFunctions]);
|
||||
testSuggestions('from a | stats a=max(b), ', [
|
||||
'var0 =',
|
||||
...allAggFunctions,
|
||||
...allEvaFunctions,
|
||||
]);
|
||||
testSuggestions('from a | stats a=min()', getFieldNamesByType('number'), '(');
|
||||
testSuggestions('from a | stats a=min(b) ', ['by', '|', ',']);
|
||||
testSuggestions('from a | stats a=min(b) ', ['by $0', '|', ',']);
|
||||
testSuggestions('from a | stats a=min(b) by ', [
|
||||
...getFieldNamesByType('any'),
|
||||
...getFunctionSignaturesByReturnType('eval', 'any', { evalMath: true }),
|
||||
...allEvaFunctions,
|
||||
'var0 =',
|
||||
]);
|
||||
testSuggestions('from a | stats a=min(b),', ['var0 =', ...allAggFunctions]);
|
||||
testSuggestions('from a | stats var0=min(b),var1=c,', ['var2 =', ...allAggFunctions]);
|
||||
testSuggestions('from a | stats a=min(b),', ['var0 =', ...allAggFunctions, ...allEvaFunctions]);
|
||||
testSuggestions('from a | stats var0=min(b),var1=c,', [
|
||||
'var2 =',
|
||||
...allAggFunctions,
|
||||
...allEvaFunctions,
|
||||
]);
|
||||
testSuggestions('from a | stats a=min(b), b=max()', getFieldNamesByType('number'));
|
||||
// @TODO: remove last 2 suggestions if possible
|
||||
testSuggestions('from a | eval var0=round(b), var1=round(c) | stats ', [
|
||||
'var2 =',
|
||||
...allAggFunctions,
|
||||
...allEvaFunctions,
|
||||
'var0',
|
||||
'var1',
|
||||
]);
|
||||
|
@ -601,7 +616,7 @@ describe('autocomplete', () => {
|
|||
// smoke testing with suggestions not at the end of the string
|
||||
testSuggestions(
|
||||
'from a | stats a = min(b) | sort b',
|
||||
['by', '|', ','],
|
||||
['by $0', '|', ','],
|
||||
27 /* " " after min(b) */
|
||||
);
|
||||
testSuggestions(
|
||||
|
@ -634,29 +649,25 @@ describe('autocomplete', () => {
|
|||
|
||||
describe('enrich', () => {
|
||||
const modes = ['any', 'coordinator', 'remote'];
|
||||
const policyNames = policies.map(({ name, suggestedAs }) => suggestedAs || name);
|
||||
for (const prevCommand of [
|
||||
'',
|
||||
'| enrich other-policy ',
|
||||
'| enrich other-policy on b ',
|
||||
'| enrich other-policy with c ',
|
||||
// '| enrich other-policy ',
|
||||
// '| enrich other-policy on b ',
|
||||
// '| enrich other-policy with c ',
|
||||
]) {
|
||||
testSuggestions(`from a ${prevCommand}| enrich `, policyNames);
|
||||
testSuggestions(
|
||||
`from a ${prevCommand}| enrich `,
|
||||
policies.map(({ name, suggestedAs }) => suggestedAs || name)
|
||||
`from a ${prevCommand}| enrich _`,
|
||||
modes.map((mode) => `_${mode}:$0`),
|
||||
'_'
|
||||
);
|
||||
testSuggestions(
|
||||
`from a ${prevCommand}| enrich [`,
|
||||
modes.map((mode) => `ccq.mode:${mode}`),
|
||||
'['
|
||||
);
|
||||
// Not suggesting duplicate setting
|
||||
testSuggestions(`from a ${prevCommand}| enrich [ccq.mode:any] [`, [], '[');
|
||||
testSuggestions(`from a ${prevCommand}| enrich [ccq.mode:`, modes, ':');
|
||||
testSuggestions(
|
||||
`from a ${prevCommand}| enrich [ccq.mode:any] `,
|
||||
policies.map(({ name, suggestedAs }) => suggestedAs || name)
|
||||
);
|
||||
testSuggestions(`from a ${prevCommand}| enrich policy `, ['on', 'with', '|']);
|
||||
for (const mode of modes) {
|
||||
testSuggestions(`from a ${prevCommand}| enrich _${mode}:`, policyNames, ':');
|
||||
testSuggestions(`from a ${prevCommand}| enrich _${mode.toUpperCase()}:`, policyNames, ':');
|
||||
testSuggestions(`from a ${prevCommand}| enrich _${camelCase(mode)}:`, policyNames, ':');
|
||||
}
|
||||
testSuggestions(`from a ${prevCommand}| enrich policy `, ['on $0', 'with $0', '|']);
|
||||
testSuggestions(`from a ${prevCommand}| enrich policy on `, [
|
||||
'stringField',
|
||||
'numberField',
|
||||
|
@ -666,7 +677,7 @@ describe('autocomplete', () => {
|
|||
'any#Char$Field',
|
||||
'kubernetes.something.something',
|
||||
]);
|
||||
testSuggestions(`from a ${prevCommand}| enrich policy on b `, ['with', '|', ',']);
|
||||
testSuggestions(`from a ${prevCommand}| enrich policy on b `, ['with $0', '|', ',']);
|
||||
testSuggestions(`from a ${prevCommand}| enrich policy on b with `, [
|
||||
'var0 =',
|
||||
...getPolicyFields('policy'),
|
||||
|
@ -1083,5 +1094,12 @@ describe('autocomplete', () => {
|
|||
suggestions.every(({ command }) => command === TRIGGER_SUGGESTION_COMMAND)
|
||||
).toBeTruthy();
|
||||
});
|
||||
it('should trigger further suggestions after enrich mode', async () => {
|
||||
const suggestions = await getSuggestionsFor('from a | enrich _any:');
|
||||
// test that all commands will retrigger suggestions
|
||||
expect(
|
||||
suggestions.every(({ command }) => command === TRIGGER_SUGGESTION_COMMAND)
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,7 +14,6 @@ import {
|
|||
columnExists,
|
||||
getColumnHit,
|
||||
getCommandDefinition,
|
||||
getCommandMode,
|
||||
getCommandOption,
|
||||
getFunctionDefinition,
|
||||
getLastCharFromTrimmed,
|
||||
|
@ -40,7 +39,6 @@ import type {
|
|||
AstProviderFn,
|
||||
ESQLAstItem,
|
||||
ESQLCommand,
|
||||
ESQLCommandMode,
|
||||
ESQLCommandOption,
|
||||
ESQLFunction,
|
||||
ESQLSingleAstItem,
|
||||
|
@ -70,7 +68,6 @@ import {
|
|||
buildVariablesDefinitions,
|
||||
buildOptionDefinition,
|
||||
buildSettingDefinitions,
|
||||
buildSettingValueDefinitions,
|
||||
} from './factories';
|
||||
import { EDITOR_MARKER } from '../shared/constants';
|
||||
import { getAstContext, removeMarkerArgFromArgsList } from '../shared/context';
|
||||
|
@ -166,9 +163,12 @@ export async function suggest(
|
|||
const unclosedBrackets = unclosedRoundBrackets + unclosedSquaredBrackets;
|
||||
// if it's a comma by the user or a forced trigger by a function argument suggestion
|
||||
// add a marker to make the expression still valid
|
||||
const charThatNeedMarkers = [',', ':'];
|
||||
if (
|
||||
context.triggerCharacter === ',' ||
|
||||
(context.triggerKind === 0 && unclosedRoundBrackets === 0) ||
|
||||
(context.triggerCharacter && charThatNeedMarkers.includes(context.triggerCharacter)) ||
|
||||
(context.triggerKind === 0 &&
|
||||
unclosedRoundBrackets === 0 &&
|
||||
getLastCharFromTrimmed(innerText) !== '_') ||
|
||||
(context.triggerCharacter === ' ' &&
|
||||
(isMathFunction(innerText, offset) || isComma(innerText[offset - 2])))
|
||||
) {
|
||||
|
@ -225,18 +225,14 @@ export async function suggest(
|
|||
);
|
||||
}
|
||||
if (astContext.type === 'setting') {
|
||||
// need this wrap/unwrap thing to make TS happy
|
||||
const { setting, ...rest } = astContext;
|
||||
if (setting && isSettingItem(setting)) {
|
||||
return getSettingArgsSuggestions(
|
||||
innerText,
|
||||
ast,
|
||||
{ setting, ...rest },
|
||||
getFieldsByType,
|
||||
getFieldsMap,
|
||||
getPolicyMetadata
|
||||
);
|
||||
}
|
||||
return getSettingArgsSuggestions(
|
||||
innerText,
|
||||
ast,
|
||||
astContext,
|
||||
getFieldsByType,
|
||||
getFieldsMap,
|
||||
getPolicyMetadata
|
||||
);
|
||||
}
|
||||
if (astContext.type === 'option') {
|
||||
// need this wrap/unwrap thing to make TS happy
|
||||
|
@ -1213,10 +1209,8 @@ async function getSettingArgsSuggestions(
|
|||
{
|
||||
command,
|
||||
node,
|
||||
setting,
|
||||
}: {
|
||||
command: ESQLCommand;
|
||||
setting: ESQLCommandMode;
|
||||
node: ESQLSingleAstItem | undefined;
|
||||
},
|
||||
getFieldsByType: GetFieldsByTypeFn,
|
||||
|
@ -1224,25 +1218,15 @@ async function getSettingArgsSuggestions(
|
|||
getPolicyMetadata: GetPolicyMetadataFn
|
||||
) {
|
||||
const suggestions = [];
|
||||
const existingSettingArgs = new Set(
|
||||
command.args
|
||||
.filter((item) => isSettingItem(item) && !item.incomplete)
|
||||
.map((item) => (isSettingItem(item) ? item.name : undefined))
|
||||
);
|
||||
|
||||
const settingDef =
|
||||
setting.name && setting.incomplete
|
||||
? getCommandMode(setting.name)
|
||||
: getCommandDefinition(command.name).modes.find(({ name }) => !existingSettingArgs.has(name));
|
||||
const settingDefs = getCommandDefinition(command.name).modes;
|
||||
|
||||
if (settingDef) {
|
||||
if (settingDefs.length) {
|
||||
const lastChar = getLastCharFromTrimmed(innerText);
|
||||
if (lastChar === '[') {
|
||||
// COMMAND [<here>
|
||||
suggestions.push(...buildSettingDefinitions(settingDef));
|
||||
} else if (lastChar === ':') {
|
||||
// COMMAND [setting: <here>
|
||||
suggestions.push(...buildSettingValueDefinitions(settingDef));
|
||||
const matchingSettingDefs = settingDefs.filter(({ prefix }) => lastChar === prefix);
|
||||
if (matchingSettingDefs.length) {
|
||||
// COMMAND _<here>
|
||||
suggestions.push(...matchingSettingDefs.flatMap(buildSettingDefinitions));
|
||||
}
|
||||
}
|
||||
return suggestions;
|
||||
|
|
|
@ -217,12 +217,8 @@ export const buildOptionDefinition = (
|
|||
detail: option.description,
|
||||
sortText: 'D',
|
||||
};
|
||||
if (option.wrapped) {
|
||||
completeItem.insertText = `${option.wrapped[0]}${option.name} $0 ${option.wrapped[1]}`;
|
||||
completeItem.insertTextRules = 4; // monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet;
|
||||
}
|
||||
if (isAssignType) {
|
||||
completeItem.insertText = `${option.name} = $0`;
|
||||
if (isAssignType || option.signature.params.length) {
|
||||
completeItem.insertText = isAssignType ? `${option.name} = $0` : `${option.name} $0`;
|
||||
completeItem.insertTextRules = 4; // monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet;
|
||||
completeItem.command = TRIGGER_SUGGESTION_COMMAND;
|
||||
}
|
||||
|
@ -233,38 +229,15 @@ export const buildSettingDefinitions = (
|
|||
setting: CommandModeDefinition
|
||||
): AutocompleteCommandDefinition[] => {
|
||||
// for now there's just a single setting with one argument
|
||||
return setting.signature.params.flatMap(({ values, valueDescriptions }) => {
|
||||
return values!.map((value, i) => {
|
||||
const completeItem: AutocompleteCommandDefinition = {
|
||||
label: `${setting.name}:${value}`,
|
||||
insertText: `${setting.name}:${value}`,
|
||||
kind: 21,
|
||||
detail: valueDescriptions
|
||||
? `${setting.description} - ${valueDescriptions[i]}`
|
||||
: setting.description,
|
||||
sortText: 'D',
|
||||
};
|
||||
return completeItem;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const buildSettingValueDefinitions = (
|
||||
setting: CommandModeDefinition
|
||||
): AutocompleteCommandDefinition[] => {
|
||||
// for now there's just a single setting with one argument
|
||||
return setting.signature.params.flatMap(({ values, valueDescriptions }) => {
|
||||
return values!.map((value, i) => {
|
||||
const completeItem: AutocompleteCommandDefinition = {
|
||||
label: value,
|
||||
insertText: value,
|
||||
kind: 21,
|
||||
detail: valueDescriptions ? valueDescriptions[i] : setting.description,
|
||||
sortText: 'D',
|
||||
};
|
||||
return completeItem;
|
||||
});
|
||||
});
|
||||
return setting.values.map(({ name, description }) => ({
|
||||
label: `${setting.prefix || ''}${name}`,
|
||||
insertText: `${setting.prefix || ''}${name}:$0`,
|
||||
insertTextRules: 4, // monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
kind: 21,
|
||||
detail: description ? `${setting.description} - ${description}` : setting.description,
|
||||
sortText: 'D',
|
||||
command: TRIGGER_SUGGESTION_COMMAND,
|
||||
}));
|
||||
};
|
||||
|
||||
export const buildNoPoliciesAvailableDefinition = (): AutocompleteCommandDefinition => ({
|
||||
|
|
|
@ -130,7 +130,7 @@ function testQuickFixesFn(
|
|||
});
|
||||
}
|
||||
|
||||
type TestArgs = [string, string[], { equalityCheck?: 'include' | 'equal' }];
|
||||
type TestArgs = [string, string[], { equalityCheck?: 'include' | 'equal' }?];
|
||||
|
||||
// Make only and skip work with our custom wrapper
|
||||
const testQuickFixes = Object.assign(testQuickFixesFn, {
|
||||
|
@ -184,9 +184,14 @@ describe('quick fixes logic', () => {
|
|||
]);
|
||||
|
||||
describe('metafields spellchecks', () => {
|
||||
testQuickFixes(`FROM index [metadata _i_ndex]`, ['_index']);
|
||||
testQuickFixes(`FROM index [metadata _id, _i_ndex]`, ['_index']);
|
||||
testQuickFixes(`FROM index [METADATA _id, _i_ndex]`, ['_index']);
|
||||
for (const isWrapped of [true, false]) {
|
||||
function setWrapping(text: string) {
|
||||
return isWrapped ? `[${text}]` : text;
|
||||
}
|
||||
testQuickFixes(`FROM index ${setWrapping('metadata _i_ndex')}`, ['_index']);
|
||||
testQuickFixes(`FROM index ${setWrapping('metadata _id, _i_ndex')}`, ['_index']);
|
||||
testQuickFixes(`FROM index ${setWrapping('METADATA _id, _i_ndex')}`, ['_index']);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -214,6 +219,15 @@ describe('quick fixes logic', () => {
|
|||
testQuickFixes(`FROM index | ENRICH poli`, ['policy']);
|
||||
testQuickFixes(`FROM index | ENRICH mypolicy`, ['policy']);
|
||||
testQuickFixes(`FROM index | ENRICH policy[`, ['policy', 'policy[]']);
|
||||
|
||||
describe('modes', () => {
|
||||
testQuickFixes(`FROM index | ENRICH _ann:policy`, ['_any']);
|
||||
const modes = ['_any', '_coordinator', '_remote'];
|
||||
for (const mode of modes) {
|
||||
testQuickFixes(`FROM index | ENRICH ${mode.replace('_', '@')}:policy`, [mode]);
|
||||
}
|
||||
testQuickFixes(`FROM index | ENRICH unknown:policy`, modes);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fixing function spellchecks', () => {
|
||||
|
@ -281,4 +295,39 @@ describe('quick fixes logic', () => {
|
|||
testQuickFixes('FROM index | DROP any#Char$Field', ['`any#Char$Field`']);
|
||||
testQuickFixes('FROM index | DROP numberField, any#Char$Field', ['`any#Char$Field`']);
|
||||
});
|
||||
|
||||
describe('callbacks', () => {
|
||||
it('should not crash if callback functions are not passed', async () => {
|
||||
const callbackMocks = getCallbackMocks();
|
||||
const statement = `from a | eval b = a | enrich policy | dissect stringField "%{firstWord}"`;
|
||||
const { model, range } = createModelAndRange(statement);
|
||||
const { errors } = await validateAst(statement, getAstAndErrors, callbackMocks);
|
||||
const monacoErrors = wrapAsMonacoMessage('error', statement, errors);
|
||||
const context = createMonacoContext(monacoErrors);
|
||||
try {
|
||||
await getActions(model, range, context, getAstAndErrors, {
|
||||
getFieldsFor: undefined,
|
||||
getSources: undefined,
|
||||
getPolicies: undefined,
|
||||
getMetaFields: undefined,
|
||||
});
|
||||
} catch {
|
||||
fail('Should not throw');
|
||||
}
|
||||
});
|
||||
|
||||
it('should not crash no callbacks are passed', async () => {
|
||||
const callbackMocks = getCallbackMocks();
|
||||
const statement = `from a | eval b = a | enrich policy | dissect stringField "%{firstWord}"`;
|
||||
const { model, range } = createModelAndRange(statement);
|
||||
const { errors } = await validateAst(statement, getAstAndErrors, callbackMocks);
|
||||
const monacoErrors = wrapAsMonacoMessage('error', statement, errors);
|
||||
const context = createMonacoContext(monacoErrors);
|
||||
try {
|
||||
await getActions(model, range, context, getAstAndErrors, undefined);
|
||||
} catch {
|
||||
fail('Should not throw');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,7 +13,12 @@ import {
|
|||
getPolicyHelper,
|
||||
getSourcesHelper,
|
||||
} from '../shared/resources_helpers';
|
||||
import { getAllFunctions, isSourceItem, shouldBeQuotedText } from '../shared/helpers';
|
||||
import {
|
||||
getAllFunctions,
|
||||
getCommandDefinition,
|
||||
isSourceItem,
|
||||
shouldBeQuotedText,
|
||||
} from '../shared/helpers';
|
||||
import { ESQLCallbacks } from '../shared/types';
|
||||
import { AstProviderFn, ESQLAst, ESQLCommand } from '../types';
|
||||
import { buildQueryForFieldsFromSource } from '../validation/helpers';
|
||||
|
@ -81,25 +86,13 @@ export function getMetaFieldsRetriever(
|
|||
|
||||
export const getCompatibleFunctionDefinitions = (
|
||||
command: string,
|
||||
option: string | undefined,
|
||||
returnTypes?: string[],
|
||||
ignored: string[] = []
|
||||
option: string | undefined
|
||||
): string[] => {
|
||||
const fnSupportedByCommand = getAllFunctions({ type: ['eval', 'agg'] }).filter(
|
||||
({ name, supportedCommands, supportedOptions }) =>
|
||||
(option ? supportedOptions?.includes(option) : supportedCommands.includes(command)) &&
|
||||
!ignored.includes(name)
|
||||
option ? supportedOptions?.includes(option) : supportedCommands.includes(command)
|
||||
);
|
||||
if (!returnTypes) {
|
||||
return fnSupportedByCommand.map(({ name }) => name);
|
||||
}
|
||||
return fnSupportedByCommand
|
||||
.filter((mathDefinition) =>
|
||||
mathDefinition.signatures.some(
|
||||
(signature) => returnTypes[0] === 'any' || returnTypes.includes(signature.returnType)
|
||||
)
|
||||
)
|
||||
.map(({ name }) => name);
|
||||
return fnSupportedByCommand.map(({ name }) => name);
|
||||
};
|
||||
|
||||
function createAction(
|
||||
|
@ -291,6 +284,32 @@ async function getSpellingActionForMetadata(
|
|||
return wrapIntoSpellingChangeAction(error, uri, possibleMetafields);
|
||||
}
|
||||
|
||||
async function getSpellingActionForEnrichMode(
|
||||
error: monaco.editor.IMarkerData,
|
||||
uri: monaco.Uri,
|
||||
queryString: string,
|
||||
ast: ESQLAst,
|
||||
_callbacks: Callbacks
|
||||
) {
|
||||
const errorText = queryString.substring(error.startColumn - 1, error.endColumn - 1);
|
||||
const commandContext =
|
||||
ast.find((command) => command.location.max > error.endColumn) || ast[ast.length - 1];
|
||||
if (!commandContext) {
|
||||
return [];
|
||||
}
|
||||
const commandDef = getCommandDefinition(commandContext.name);
|
||||
const allModes =
|
||||
commandDef.modes?.flatMap(({ values, prefix }) =>
|
||||
values.map(({ name }) => `${prefix || ''}${name}`)
|
||||
) || [];
|
||||
const possibleEnrichModes = await getSpellingPossibilities(async () => allModes, errorText);
|
||||
// if no possible solution is found, push all modes
|
||||
if (!possibleEnrichModes.length) {
|
||||
possibleEnrichModes.push(...allModes);
|
||||
}
|
||||
return wrapIntoSpellingChangeAction(error, uri, possibleEnrichModes);
|
||||
}
|
||||
|
||||
function wrapIntoSpellingChangeAction(
|
||||
error: monaco.editor.IMarkerData,
|
||||
uri: monaco.Uri,
|
||||
|
@ -414,6 +433,16 @@ export async function getActions(
|
|||
)
|
||||
);
|
||||
break;
|
||||
case 'unsupportedSettingCommandValue':
|
||||
const enrichModeSpellChanges = await getSpellingActionForEnrichMode(
|
||||
error,
|
||||
model.uri,
|
||||
innerText,
|
||||
ast,
|
||||
callbacks
|
||||
);
|
||||
actions.push(...enrichModeSpellChanges);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -13,13 +13,13 @@ function createMathDefinition(
|
|||
name: string,
|
||||
types: Array<string | string[]>,
|
||||
description: string,
|
||||
warning?: FunctionDefinition['warning']
|
||||
validate?: FunctionDefinition['validate']
|
||||
): FunctionDefinition {
|
||||
return {
|
||||
type: 'builtin',
|
||||
name,
|
||||
description,
|
||||
supportedCommands: ['eval', 'where', 'row'],
|
||||
supportedCommands: ['eval', 'where', 'row', 'stats'],
|
||||
supportedOptions: ['by'],
|
||||
signatures: types.map((type) => {
|
||||
if (Array.isArray(type)) {
|
||||
|
@ -39,7 +39,7 @@ function createMathDefinition(
|
|||
returnType: type,
|
||||
};
|
||||
}),
|
||||
warning,
|
||||
validate,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -51,7 +51,7 @@ function createComparisonDefinition(
|
|||
name: string;
|
||||
description: string;
|
||||
},
|
||||
warning?: FunctionDefinition['warning']
|
||||
validate?: FunctionDefinition['validate']
|
||||
): FunctionDefinition {
|
||||
return {
|
||||
type: 'builtin' as const,
|
||||
|
@ -59,6 +59,7 @@ function createComparisonDefinition(
|
|||
description,
|
||||
supportedCommands: ['eval', 'where', 'row'],
|
||||
supportedOptions: ['by'],
|
||||
validate,
|
||||
signatures: [
|
||||
{
|
||||
params: [
|
||||
|
|
|
@ -7,9 +7,15 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { isColumnItem, isSettingItem } from '../shared/helpers';
|
||||
import { ESQLColumn, ESQLCommand, ESQLCommandMode, ESQLMessage } from '../types';
|
||||
import { ccqMode } from './settings';
|
||||
import {
|
||||
getFunctionDefinition,
|
||||
isAssignment,
|
||||
isAssignmentComplete,
|
||||
isColumnItem,
|
||||
isFunctionItem,
|
||||
} from '../shared/helpers';
|
||||
import type { ESQLColumn, ESQLCommand, ESQLAstItem, ESQLMessage } from '../types';
|
||||
import { enrichModes } from './settings';
|
||||
import {
|
||||
appendSeparatorOption,
|
||||
asOption,
|
||||
|
@ -88,6 +94,64 @@ export const commandDefinitions: CommandDefinition[] = [
|
|||
code: 'statsNoArguments',
|
||||
});
|
||||
}
|
||||
|
||||
// now that all functions are supported, there's a specific check to perform
|
||||
// unfortunately the logic here is a bit complex as it needs to dig deeper into the args
|
||||
// until an agg function is detected
|
||||
// in the long run this might be integrated into the validation function
|
||||
const fnArg = command.args.filter(isFunctionItem);
|
||||
if (fnArg.length) {
|
||||
function isAggFunction(arg: ESQLAstItem) {
|
||||
return isFunctionItem(arg) && getFunctionDefinition(arg.name)?.type === 'agg';
|
||||
}
|
||||
function isOtherFunction(arg: ESQLAstItem) {
|
||||
return isFunctionItem(arg) && getFunctionDefinition(arg.name)?.type !== 'agg';
|
||||
}
|
||||
function isOtherFunctionWithAggInside(arg: ESQLAstItem) {
|
||||
return (
|
||||
isFunctionItem(arg) &&
|
||||
isOtherFunction(arg) &&
|
||||
arg.args.filter(isFunctionItem).some(
|
||||
// this is recursive as builtin fns can be wrapped one withins another
|
||||
(subArg): boolean =>
|
||||
isAggFunction(subArg) ||
|
||||
(isOtherFunction(subArg) ? isOtherFunctionWithAggInside(subArg) : false)
|
||||
)
|
||||
);
|
||||
}
|
||||
// which is the presence of at least one agg type function at root level
|
||||
const hasAggFunction = fnArg.some(isAggFunction);
|
||||
// or as builtin function arg with an agg function as sub arg
|
||||
const hasAggFunctionWithinBuiltin = fnArg
|
||||
.filter((arg) => !isAssignment(arg))
|
||||
.some(isOtherFunctionWithAggInside);
|
||||
|
||||
// assignment requires a special handling
|
||||
const hasAggFunctionWithinAssignment = fnArg
|
||||
.filter((arg) => isAssignment(arg) && isAssignmentComplete(arg))
|
||||
// extract the right hand side of the assignments
|
||||
.flatMap((arg) => arg.args[1])
|
||||
.filter(isFunctionItem)
|
||||
// now check that they are either agg functions
|
||||
// or builtin functions with an agg function as sub arg
|
||||
.some((arg) => isAggFunction(arg) || isOtherFunctionWithAggInside(arg));
|
||||
|
||||
if (!hasAggFunction && !hasAggFunctionWithinBuiltin && !hasAggFunctionWithinAssignment) {
|
||||
messages.push({
|
||||
location: command.location,
|
||||
text: i18n.translate('monaco.esql.validation.noNestedArgumentSupport', {
|
||||
defaultMessage:
|
||||
"Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [{name}] of type [{argType}]",
|
||||
values: {
|
||||
name: fnArg[0].name,
|
||||
argType: getFunctionDefinition(fnArg[0].name)?.signatures[0].returnType,
|
||||
},
|
||||
}),
|
||||
type: 'error',
|
||||
code: 'noNestedArgumentSupport',
|
||||
});
|
||||
}
|
||||
}
|
||||
return messages;
|
||||
},
|
||||
},
|
||||
|
@ -149,22 +213,6 @@ export const commandDefinitions: CommandDefinition[] = [
|
|||
multipleParams: true,
|
||||
params: [{ name: 'column', type: 'column', wildcards: true }],
|
||||
},
|
||||
validate: (command: ESQLCommand) => {
|
||||
// the command name is automatically converted into KEEP by the ast_walker
|
||||
// so validate the actual text
|
||||
const messages: ESQLMessage[] = [];
|
||||
if (/^project/.test(command.text.toLowerCase())) {
|
||||
messages.push({
|
||||
location: command.location,
|
||||
text: i18n.translate('monaco.esql.validation.projectCommandDeprecated', {
|
||||
defaultMessage: 'PROJECT command is no longer supported, please use KEEP instead',
|
||||
}),
|
||||
type: 'warning',
|
||||
code: 'projectCommandDeprecated',
|
||||
});
|
||||
}
|
||||
return messages;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'drop',
|
||||
|
@ -304,41 +352,10 @@ export const commandDefinitions: CommandDefinition[] = [
|
|||
'… | enrich my-policy on pivotField with a = enrichFieldA, b = enrichFieldB',
|
||||
],
|
||||
options: [onOption, withOption],
|
||||
modes: [ccqMode],
|
||||
modes: [enrichModes],
|
||||
signature: {
|
||||
multipleParams: false,
|
||||
params: [{ name: 'policyName', type: 'source', innerType: 'policy' }],
|
||||
},
|
||||
validate: (command: ESQLCommand) => {
|
||||
const messages: ESQLMessage[] = [];
|
||||
if (command.args.some(isSettingItem)) {
|
||||
const settings = command.args.filter(isSettingItem);
|
||||
const settingCounters: Record<string, number> = {};
|
||||
const settingLookup: Record<string, ESQLCommandMode> = {};
|
||||
for (const setting of settings) {
|
||||
if (!settingCounters[setting.name]) {
|
||||
settingCounters[setting.name] = 0;
|
||||
settingLookup[setting.name] = setting;
|
||||
}
|
||||
settingCounters[setting.name]++;
|
||||
}
|
||||
const duplicateSettings = Object.entries(settingCounters).filter(([_, count]) => count > 1);
|
||||
messages.push(
|
||||
...duplicateSettings.map(([name]) => ({
|
||||
location: settingLookup[name].location,
|
||||
text: i18n.translate('monaco.esql.validation.duplicateSettingWarning', {
|
||||
defaultMessage:
|
||||
'Multiple definition of setting [{name}]. Only last one will be applied.',
|
||||
values: {
|
||||
name,
|
||||
},
|
||||
}),
|
||||
type: 'warning' as const,
|
||||
code: 'duplicateSettingWarning',
|
||||
}))
|
||||
);
|
||||
}
|
||||
return messages;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
@ -7,8 +7,32 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { isLiteralItem } from '../shared/helpers';
|
||||
import { ESQLFunction } from '../types';
|
||||
import { FunctionDefinition } from './types';
|
||||
|
||||
const validateLogFunctions = (fnDef: ESQLFunction) => {
|
||||
const messages = [];
|
||||
// do not really care here about the base and field
|
||||
// just need to check both values are not negative
|
||||
for (const arg of fnDef.args) {
|
||||
if (isLiteralItem(arg) && arg.value < 0) {
|
||||
messages.push({
|
||||
type: 'warning' as const,
|
||||
code: 'logOfNegativeValue',
|
||||
text: i18n.translate('monaco.esql.divide.warning.logOfNegativeValue', {
|
||||
defaultMessage: 'Log of a negative number results in null: {value}',
|
||||
values: {
|
||||
value: arg.value,
|
||||
},
|
||||
}),
|
||||
location: arg.location,
|
||||
});
|
||||
}
|
||||
}
|
||||
return messages;
|
||||
};
|
||||
|
||||
export const evalFunctionsDefinitions: FunctionDefinition[] = [
|
||||
{
|
||||
name: 'round',
|
||||
|
@ -68,6 +92,29 @@ export const evalFunctionsDefinitions: FunctionDefinition[] = [
|
|||
examples: [`from index | eval log10_value = log10(field)`],
|
||||
},
|
||||
],
|
||||
validate: validateLogFunctions,
|
||||
},
|
||||
|
||||
{
|
||||
name: 'log',
|
||||
description: i18n.translate('monaco.esql.definitions.logDoc', {
|
||||
defaultMessage:
|
||||
'A scalar function log(based, value) returns the logarithm of a value for a particular base, as specified in the argument',
|
||||
}),
|
||||
signatures: [
|
||||
{
|
||||
params: [
|
||||
{ name: 'baseOrField', type: 'number' },
|
||||
{ name: 'field', type: 'number', optional: true },
|
||||
],
|
||||
returnType: 'number',
|
||||
examples: [
|
||||
`from index | eval log2_value = log(2, field)`,
|
||||
`from index | eval loge_value = log(field)`,
|
||||
],
|
||||
},
|
||||
],
|
||||
validate: validateLogFunctions,
|
||||
},
|
||||
{
|
||||
name: 'pow',
|
||||
|
@ -1050,7 +1097,7 @@ export const evalFunctionsDefinitions: FunctionDefinition[] = [
|
|||
.sort(({ name: a }, { name: b }) => a.localeCompare(b))
|
||||
.map((def) => ({
|
||||
...def,
|
||||
supportedCommands: ['eval', 'where', 'row'],
|
||||
supportedCommands: ['stats', 'eval', 'where', 'row'],
|
||||
supportedOptions: ['by'],
|
||||
type: 'eval',
|
||||
}));
|
||||
|
|
|
@ -33,10 +33,20 @@ export const metadataOption: CommandOptionsDefinition = {
|
|||
params: [{ name: 'column', type: 'column' }],
|
||||
},
|
||||
optional: true,
|
||||
wrapped: ['[', ']'],
|
||||
skipCommonValidation: true,
|
||||
validate: (option, command, references) => {
|
||||
const messages: ESQLMessage[] = [];
|
||||
// need to test the parent command here
|
||||
if (/\[metadata/i.test(command.text)) {
|
||||
messages.push({
|
||||
location: option.location,
|
||||
text: i18n.translate('monaco.esql.validation.metadataBracketsDeprecation', {
|
||||
defaultMessage: "Square brackets '[]' need to be removed from FROM METADATA declaration",
|
||||
}),
|
||||
type: 'warning',
|
||||
code: 'metadataBracketsDeprecation',
|
||||
});
|
||||
}
|
||||
const fields = option.args.filter(isColumnItem);
|
||||
const metadataFieldsAvailable = references as unknown as Set<string>;
|
||||
if (metadataFieldsAvailable.size > 0) {
|
||||
|
|
|
@ -9,30 +9,30 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { CommandModeDefinition } from './types';
|
||||
|
||||
export const ccqMode: CommandModeDefinition = {
|
||||
export const enrichModes: CommandModeDefinition = {
|
||||
name: 'ccq.mode',
|
||||
description: i18n.translate('monaco.esql.definitions.ccqModeDoc', {
|
||||
defaultMessage: 'Cross-clusters query mode',
|
||||
}),
|
||||
signature: {
|
||||
multipleParams: false,
|
||||
params: [
|
||||
{
|
||||
name: 'mode',
|
||||
type: 'string',
|
||||
values: ['any', 'coordinator', 'remote'],
|
||||
valueDescriptions: [
|
||||
i18n.translate('monaco.esql.definitions.ccqAnyDoc', {
|
||||
defaultMessage: 'Enrich takes place on any cluster',
|
||||
}),
|
||||
i18n.translate('monaco.esql.definitions.ccqCoordinatorDoc', {
|
||||
defaultMessage: 'Enrich takes place on the coordinating cluster receiving an ES|QL',
|
||||
}),
|
||||
i18n.translate('monaco.esql.definitions.ccqRemoteDoc', {
|
||||
defaultMessage: 'Enrich takes place on the cluster hosting the target index.',
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
prefix: '_',
|
||||
values: [
|
||||
{
|
||||
name: 'any',
|
||||
description: i18n.translate('monaco.esql.definitions.ccqAnyDoc', {
|
||||
defaultMessage: 'Enrich takes place on any cluster',
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'coordinator',
|
||||
description: i18n.translate('monaco.esql.definitions.ccqCoordinatorDoc', {
|
||||
defaultMessage: 'Enrich takes place on the coordinating cluster receiving an ES|QL',
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'remote',
|
||||
description: i18n.translate('monaco.esql.definitions.ccqRemoteDoc', {
|
||||
defaultMessage: 'Enrich takes place on the cluster hosting the target index.',
|
||||
}),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -29,7 +29,7 @@ export interface FunctionDefinition {
|
|||
returnType: string;
|
||||
examples?: string[];
|
||||
}>;
|
||||
warning?: (fnDef: ESQLFunction) => ESQLMessage[];
|
||||
validate?: (fnDef: ESQLFunction) => ESQLMessage[];
|
||||
}
|
||||
|
||||
export interface CommandBaseDefinition {
|
||||
|
@ -64,9 +64,11 @@ export interface CommandOptionsDefinition extends CommandBaseDefinition {
|
|||
) => ESQLMessage[];
|
||||
}
|
||||
|
||||
export interface CommandModeDefinition extends CommandBaseDefinition {
|
||||
export interface CommandModeDefinition {
|
||||
name: string;
|
||||
description: string;
|
||||
values: Array<{ name: string; description: string }>;
|
||||
prefix?: string;
|
||||
}
|
||||
|
||||
export interface CommandDefinition extends CommandBaseDefinition {
|
||||
|
|
|
@ -14,6 +14,7 @@ import { AstListener } from '../ast_factory';
|
|||
import { getHoverItem } from './hover';
|
||||
import { getFunctionDefinition } from '../shared/helpers';
|
||||
import { getFunctionSignatures } from '../definitions/helpers';
|
||||
import { enrichModes } from '../definitions/settings';
|
||||
|
||||
const fields: Array<{ name: string; type: string; suggestedAs?: string }> = [
|
||||
...['string', 'number', 'date', 'boolean', 'ip'].map((type) => ({
|
||||
|
@ -187,6 +188,16 @@ describe('hover', () => {
|
|||
testSuggestions(`from a | enrich policy`, 'policy', createPolicyContent);
|
||||
testSuggestions(`from a | enrich policy on b `, 'policy', createPolicyContent);
|
||||
testSuggestions(`from a | enrich policy on b `, 'non-policy', createPolicyContent);
|
||||
|
||||
describe('ccq mode', () => {
|
||||
for (const mode of enrichModes.values) {
|
||||
testSuggestions(
|
||||
`from a | enrich ${enrichModes.prefix || ''}${mode.name}:policy`,
|
||||
`${enrichModes.prefix || ''}${mode.name}`,
|
||||
() => [enrichModes.description, `**${mode.name}**: ${mode.description}`]
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('functions', () => {
|
||||
function createFunctionContent(fn: string) {
|
||||
|
|
|
@ -10,7 +10,13 @@ import { i18n } from '@kbn/i18n';
|
|||
import type { monaco } from '../../../../monaco_imports';
|
||||
import { getFunctionSignatures } from '../definitions/helpers';
|
||||
import { getAstContext } from '../shared/context';
|
||||
import { monacoPositionToOffset, getFunctionDefinition, isSourceItem } from '../shared/helpers';
|
||||
import {
|
||||
monacoPositionToOffset,
|
||||
getFunctionDefinition,
|
||||
isSourceItem,
|
||||
isSettingItem,
|
||||
getCommandDefinition,
|
||||
} from '../shared/helpers';
|
||||
import { getPolicyHelper } from '../shared/resources_helpers';
|
||||
import { ESQLCallbacks } from '../shared/types';
|
||||
import type { AstProviderFn } from '../types';
|
||||
|
@ -47,32 +53,47 @@ export async function getHoverItem(
|
|||
}
|
||||
|
||||
if (astContext.type === 'expression') {
|
||||
if (
|
||||
astContext.node &&
|
||||
isSourceItem(astContext.node) &&
|
||||
astContext.node.sourceType === 'policy'
|
||||
) {
|
||||
const policyMetadata = await getPolicyMetadata(astContext.node.name);
|
||||
if (policyMetadata) {
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
value: `${i18n.translate('monaco.esql.hover.policyIndexes', {
|
||||
defaultMessage: '**Indexes**',
|
||||
})}: ${policyMetadata.sourceIndices.join(', ')}`,
|
||||
},
|
||||
{
|
||||
value: `${i18n.translate('monaco.esql.hover.policyMatchingField', {
|
||||
defaultMessage: '**Matching field**',
|
||||
})}: ${policyMetadata.matchField}`,
|
||||
},
|
||||
{
|
||||
value: `${i18n.translate('monaco.esql.hover.policyEnrichedFields', {
|
||||
defaultMessage: '**Fields**',
|
||||
})}: ${policyMetadata.enrichFields.join(', ')}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
if (astContext.node) {
|
||||
if (isSourceItem(astContext.node) && astContext.node.sourceType === 'policy') {
|
||||
const policyMetadata = await getPolicyMetadata(astContext.node.name);
|
||||
if (policyMetadata) {
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
value: `${i18n.translate('monaco.esql.hover.policyIndexes', {
|
||||
defaultMessage: '**Indexes**',
|
||||
})}: ${policyMetadata.sourceIndices.join(', ')}`,
|
||||
},
|
||||
{
|
||||
value: `${i18n.translate('monaco.esql.hover.policyMatchingField', {
|
||||
defaultMessage: '**Matching field**',
|
||||
})}: ${policyMetadata.matchField}`,
|
||||
},
|
||||
{
|
||||
value: `${i18n.translate('monaco.esql.hover.policyEnrichedFields', {
|
||||
defaultMessage: '**Fields**',
|
||||
})}: ${policyMetadata.enrichFields.join(', ')}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
if (isSettingItem(astContext.node)) {
|
||||
const commandDef = getCommandDefinition(astContext.command.name);
|
||||
const settingDef = commandDef?.modes.find(({ values }) =>
|
||||
values.some(({ name }) => name === astContext.node!.name)
|
||||
);
|
||||
if (settingDef) {
|
||||
const mode = settingDef.values.find(({ name }) => name === astContext.node!.name)!;
|
||||
return {
|
||||
contents: [
|
||||
{ value: settingDef.description },
|
||||
{
|
||||
value: `**${mode.name}**: ${mode.description}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { enrichModes } from '../definitions/settings';
|
||||
import type {
|
||||
ESQLAstItem,
|
||||
ESQLSingleAstItem,
|
||||
|
@ -152,8 +153,9 @@ export function getAstContext(innerText: string, ast: ESQLAst, offset: number) {
|
|||
// command ... by <here>
|
||||
return { type: 'option' as const, command, node, option, setting };
|
||||
}
|
||||
if (node.type === 'mode' || option) {
|
||||
// command [<here>
|
||||
// for now it's only an enrich thing
|
||||
if (node.type === 'source' && node.text === enrichModes.prefix) {
|
||||
// command _<here>
|
||||
return { type: 'setting' as const, command, node, option, setting };
|
||||
}
|
||||
}
|
||||
|
@ -184,9 +186,6 @@ export function getAstContext(innerText: string, ast: ESQLAst, offset: number) {
|
|||
if (option) {
|
||||
return { type: 'option' as const, command, node, option, setting };
|
||||
}
|
||||
if (setting?.incomplete) {
|
||||
return { type: 'setting' as const, command, node, option, setting };
|
||||
}
|
||||
}
|
||||
|
||||
// command a ... <here> OR command a = ... <here>
|
||||
|
|
|
@ -22,10 +22,8 @@ import {
|
|||
withOption,
|
||||
appendSeparatorOption,
|
||||
} from '../definitions/options';
|
||||
import { ccqMode } from '../definitions/settings';
|
||||
import {
|
||||
CommandDefinition,
|
||||
CommandModeDefinition,
|
||||
CommandOptionsDefinition,
|
||||
FunctionDefinition,
|
||||
SignatureArgType,
|
||||
|
@ -219,10 +217,6 @@ export function getCommandOption(optionName: CommandOptionsDefinition['name']) {
|
|||
);
|
||||
}
|
||||
|
||||
export function getCommandMode(settingName: CommandModeDefinition['name']) {
|
||||
return [ccqMode].find(({ name }) => name === settingName);
|
||||
}
|
||||
|
||||
function compareLiteralType(argTypes: string, item: ESQLLiteral) {
|
||||
if (item.literalType !== 'string') {
|
||||
return argTypes === item.literalType;
|
||||
|
|
|
@ -46,7 +46,6 @@ export interface ESQLCommandOption extends ESQLAstBaseItem {
|
|||
|
||||
export interface ESQLCommandMode extends ESQLAstBaseItem {
|
||||
type: 'mode';
|
||||
args: ESQLAstItem[];
|
||||
}
|
||||
|
||||
export interface ESQLFunction extends ESQLAstBaseItem {
|
||||
|
|
|
@ -59,8 +59,13 @@ function getMessageAndTypeFromId<K extends ErrorTypes>({
|
|||
return {
|
||||
message: i18n.translate('monaco.esql.validation.wrongArgumentNumber', {
|
||||
defaultMessage:
|
||||
'Error building [{fn}]: expects exactly {numArgs, plural, one {one argument} other {{numArgs} arguments}}, passed {passedArgs} instead.',
|
||||
values: { fn: out.fn, numArgs: out.numArgs, passedArgs: out.passedArgs },
|
||||
'Error building [{fn}]: expects {canHaveMoreArgs, plural, =0 {exactly } other {}}{numArgs, plural, one {one argument} other {{numArgs} arguments}}, passed {passedArgs} instead.',
|
||||
values: {
|
||||
fn: out.fn,
|
||||
numArgs: out.numArgs,
|
||||
passedArgs: out.passedArgs,
|
||||
canHaveMoreArgs: out.exactly,
|
||||
},
|
||||
}),
|
||||
};
|
||||
case 'noNestedArgumentSupport':
|
||||
|
@ -209,9 +214,8 @@ function getMessageAndTypeFromId<K extends ErrorTypes>({
|
|||
return {
|
||||
message: i18n.translate('monaco.esql.validation.unsupportedSettingValue', {
|
||||
defaultMessage:
|
||||
'Unrecognized value [{value}], {command} [{setting}] needs to be one of [{expected}]',
|
||||
'Unrecognized value [{value}] for {command}, mode needs to be one of [{expected}]',
|
||||
values: {
|
||||
setting: out.setting,
|
||||
expected: out.expected,
|
||||
value: out.value,
|
||||
command: out.command,
|
||||
|
|
|
@ -101,7 +101,7 @@ export async function retrieveFieldsFromStringSources(
|
|||
commands: ESQLCommand[],
|
||||
callbacks?: ESQLCallbacks
|
||||
): Promise<Map<string, ESQLRealField>> {
|
||||
if (!callbacks) {
|
||||
if (!callbacks || !callbacks?.getMetaFields) {
|
||||
return new Map();
|
||||
}
|
||||
const customQuery = buildQueryForFieldsForStringSources(queryString, commands);
|
||||
|
|
|
@ -47,7 +47,7 @@ export interface ValidationErrors {
|
|||
};
|
||||
wrongArgumentNumber: {
|
||||
message: string;
|
||||
type: { fn: string; numArgs: number; passedArgs: number };
|
||||
type: { fn: string; numArgs: number; passedArgs: number; exactly: number };
|
||||
};
|
||||
unknownColumn: {
|
||||
message: string;
|
||||
|
@ -123,7 +123,7 @@ export interface ValidationErrors {
|
|||
};
|
||||
unsupportedSettingCommandValue: {
|
||||
message: string;
|
||||
type: { command: string; setting: string; value: string; expected: string };
|
||||
type: { command: string; value: string; expected: string };
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -27,21 +27,26 @@ const fieldTypes = ['number', 'date', 'boolean', 'ip', 'string', 'cartesian_poin
|
|||
|
||||
function getCallbackMocks() {
|
||||
return {
|
||||
getFieldsFor: jest.fn(async ({ query }) =>
|
||||
/enrich/.test(query)
|
||||
? [
|
||||
{ name: 'otherField', type: 'string' },
|
||||
{ name: 'yetAnotherField', type: 'number' },
|
||||
]
|
||||
: /unsupported_index/.test(query)
|
||||
? [{ name: 'unsupported_field', type: 'unsupported' }]
|
||||
: [
|
||||
...fieldTypes.map((type) => ({ name: `${camelCase(type)}Field`, type })),
|
||||
{ name: 'any#Char$Field', type: 'number' },
|
||||
{ name: 'kubernetes.something.something', type: 'number' },
|
||||
{ name: '@timestamp', type: 'date' },
|
||||
]
|
||||
),
|
||||
getFieldsFor: jest.fn(async ({ query }) => {
|
||||
if (/enrich/.test(query)) {
|
||||
return [
|
||||
{ name: 'otherField', type: 'string' },
|
||||
{ name: 'yetAnotherField', type: 'number' },
|
||||
];
|
||||
}
|
||||
if (/unsupported_index/.test(query)) {
|
||||
return [{ name: 'unsupported_field', type: 'unsupported' }];
|
||||
}
|
||||
if (/dissect|grok/.test(query)) {
|
||||
return [{ name: 'firstWord', type: 'string' }];
|
||||
}
|
||||
return [
|
||||
...fieldTypes.map((type) => ({ name: `${camelCase(type)}Field`, type })),
|
||||
{ name: 'any#Char$Field', type: 'number' },
|
||||
{ name: 'kubernetes.something.something', type: 'number' },
|
||||
{ name: '@timestamp', type: 'date' },
|
||||
];
|
||||
}),
|
||||
getSources: jest.fn(async () =>
|
||||
['a', 'index', 'otherIndex', '.secretIndex', 'my-index', 'unsupported_index'].map((name) => ({
|
||||
name,
|
||||
|
@ -250,7 +255,7 @@ describe('validation logic', () => {
|
|||
"SyntaxError: missing {QUOTED_IDENTIFIER, FROM_UNQUOTED_IDENTIFIER} at '<EOF>'",
|
||||
]);
|
||||
testErrorsAndWarnings(`from assignment = 1`, [
|
||||
'SyntaxError: expected {<EOF>, PIPE, COMMA, OPENING_BRACKET} but found "="',
|
||||
'SyntaxError: expected {<EOF>, PIPE, COMMA, OPENING_BRACKET, METADATA} but found "="',
|
||||
'Unknown index [assignment]',
|
||||
]);
|
||||
testErrorsAndWarnings(`from index`, []);
|
||||
|
@ -262,21 +267,53 @@ describe('validation logic', () => {
|
|||
testErrorsAndWarnings(`from index, missingIndex`, ['Unknown index [missingIndex]']);
|
||||
testErrorsAndWarnings(`from fn()`, ['Unknown index [fn()]']);
|
||||
testErrorsAndWarnings(`from average()`, ['Unknown index [average()]']);
|
||||
testErrorsAndWarnings(`from index [METADATA _id]`, []);
|
||||
testErrorsAndWarnings(`from index [metadata _id]`, []);
|
||||
for (const isWrapped of [true, false]) {
|
||||
function setWrapping(option: string) {
|
||||
return isWrapped ? `[${option}]` : option;
|
||||
}
|
||||
function addBracketsWarning() {
|
||||
return isWrapped
|
||||
? ["Square brackets '[]' need to be removed from FROM METADATA declaration"]
|
||||
: [];
|
||||
}
|
||||
testErrorsAndWarnings(`from index ${setWrapping('METADATA _id')}`, [], addBracketsWarning());
|
||||
testErrorsAndWarnings(`from index ${setWrapping('metadata _id')}`, [], addBracketsWarning());
|
||||
|
||||
testErrorsAndWarnings(`from index [METADATA _id, _source]`, []);
|
||||
testErrorsAndWarnings(`from index [METADATA _id, _source2]`, [
|
||||
'Metadata field [_source2] is not available. Available metadata fields are: [_id, _source]',
|
||||
]);
|
||||
testErrorsAndWarnings(`from index [metadata _id, _source] [METADATA _id2]`, [
|
||||
'SyntaxError: expected {<EOF>, PIPE} but found "["',
|
||||
]);
|
||||
testErrorsAndWarnings(`from index metadata _id`, [
|
||||
'SyntaxError: expected {<EOF>, PIPE, COMMA, OPENING_BRACKET} but found "metadata"',
|
||||
]);
|
||||
testErrorsAndWarnings(
|
||||
`from index ${setWrapping('METADATA _id, _source')}`,
|
||||
[],
|
||||
addBracketsWarning()
|
||||
);
|
||||
testErrorsAndWarnings(
|
||||
`from index ${setWrapping('METADATA _id, _source2')}`,
|
||||
[
|
||||
'Metadata field [_source2] is not available. Available metadata fields are: [_id, _source]',
|
||||
],
|
||||
addBracketsWarning()
|
||||
);
|
||||
testErrorsAndWarnings(
|
||||
`from index ${setWrapping('metadata _id, _source')} ${setWrapping('METADATA _id2')}`,
|
||||
[
|
||||
isWrapped
|
||||
? 'SyntaxError: expected {COMMA, CLOSING_BRACKET} but found "["'
|
||||
: 'SyntaxError: expected {<EOF>, PIPE, COMMA} but found "METADATA"',
|
||||
],
|
||||
addBracketsWarning()
|
||||
);
|
||||
|
||||
testErrorsAndWarnings(
|
||||
`from remote-ccs:indexes ${setWrapping('METADATA _id')}`,
|
||||
[],
|
||||
addBracketsWarning()
|
||||
);
|
||||
testErrorsAndWarnings(
|
||||
`from *:indexes ${setWrapping('METADATA _id')}`,
|
||||
[],
|
||||
addBracketsWarning()
|
||||
);
|
||||
}
|
||||
testErrorsAndWarnings(`from index (metadata _id)`, [
|
||||
'SyntaxError: expected {<EOF>, PIPE, COMMA, OPENING_BRACKET} but found "(metadata"',
|
||||
'SyntaxError: expected {<EOF>, PIPE, COMMA, OPENING_BRACKET, METADATA} but found "(metadata"',
|
||||
]);
|
||||
testErrorsAndWarnings(`from ind*, other*`, []);
|
||||
testErrorsAndWarnings(`from index*`, []);
|
||||
|
@ -289,8 +326,6 @@ describe('validation logic', () => {
|
|||
testErrorsAndWarnings(`from remote-*:indexes`, []);
|
||||
testErrorsAndWarnings(`from remote-ccs:indexes`, []);
|
||||
testErrorsAndWarnings(`from a, remote-ccs:indexes`, []);
|
||||
testErrorsAndWarnings(`from remote-ccs:indexes [METADATA _id]`, []);
|
||||
testErrorsAndWarnings(`from *:indexes [METADATA _id]`, []);
|
||||
testErrorsAndWarnings('from .secretIndex', []);
|
||||
testErrorsAndWarnings('from my-index', []);
|
||||
testErrorsAndWarnings('from numberField', ['Unknown index [numberField]']);
|
||||
|
@ -374,7 +409,7 @@ describe('validation logic', () => {
|
|||
);
|
||||
|
||||
testErrorsAndWarnings(`row var = ${signatureStringCorrect}`, []);
|
||||
testErrorsAndWarnings(`row ${signatureStringCorrect}`);
|
||||
testErrorsAndWarnings(`row ${signatureStringCorrect}`, []);
|
||||
|
||||
if (alias) {
|
||||
for (const otherName of alias) {
|
||||
|
@ -412,7 +447,7 @@ describe('validation logic', () => {
|
|||
)[0].declaration
|
||||
);
|
||||
|
||||
testErrorsAndWarnings(`row var = ${signatureString}`);
|
||||
testErrorsAndWarnings(`row var = ${signatureString}`, []);
|
||||
|
||||
const wrongFieldMapping = params.map(({ name: _name, type, ...rest }) => {
|
||||
const typeString = type;
|
||||
|
@ -580,26 +615,18 @@ describe('validation logic', () => {
|
|||
'Unknown column [missingField]',
|
||||
]);
|
||||
testErrorsAndWarnings('from index | keep `any#Char$Field`', []);
|
||||
testErrorsAndWarnings(
|
||||
'from index | project ',
|
||||
[`SyntaxError: missing {QUOTED_IDENTIFIER, UNQUOTED_ID_PATTERN} at '<EOF>'`],
|
||||
['PROJECT command is no longer supported, please use KEEP instead']
|
||||
);
|
||||
testErrorsAndWarnings(
|
||||
'from index | project stringField, numberField, dateField',
|
||||
[],
|
||||
['PROJECT command is no longer supported, please use KEEP instead']
|
||||
);
|
||||
testErrorsAndWarnings(
|
||||
'from index | PROJECT stringField, numberField, dateField',
|
||||
[],
|
||||
['PROJECT command is no longer supported, please use KEEP instead']
|
||||
);
|
||||
testErrorsAndWarnings(
|
||||
'from index | project missingField, numberField, dateField',
|
||||
['Unknown column [missingField]'],
|
||||
['PROJECT command is no longer supported, please use KEEP instead']
|
||||
);
|
||||
testErrorsAndWarnings('from index | project ', [
|
||||
`SyntaxError: expected {DISSECT, DROP, ENRICH, EVAL, GROK, INLINESTATS, KEEP, LIMIT, MV_EXPAND, RENAME, SORT, STATS, WHERE} but found \"project\"`,
|
||||
]);
|
||||
testErrorsAndWarnings('from index | project stringField, numberField, dateField', [
|
||||
`SyntaxError: expected {DISSECT, DROP, ENRICH, EVAL, GROK, INLINESTATS, KEEP, LIMIT, MV_EXPAND, RENAME, SORT, STATS, WHERE} but found \"project\"`,
|
||||
]);
|
||||
testErrorsAndWarnings('from index | PROJECT stringField, numberField, dateField', [
|
||||
`SyntaxError: expected {DISSECT, DROP, ENRICH, EVAL, GROK, INLINESTATS, KEEP, LIMIT, MV_EXPAND, RENAME, SORT, STATS, WHERE} but found \"PROJECT\"`,
|
||||
]);
|
||||
testErrorsAndWarnings('from index | project missingField, numberField, dateField', [
|
||||
`SyntaxError: expected {DISSECT, DROP, ENRICH, EVAL, GROK, INLINESTATS, KEEP, LIMIT, MV_EXPAND, RENAME, SORT, STATS, WHERE} but found \"project\"`,
|
||||
]);
|
||||
testErrorsAndWarnings('from index | keep s*', []);
|
||||
testErrorsAndWarnings('from index | keep *Field', []);
|
||||
testErrorsAndWarnings('from index | keep s*Field', []);
|
||||
|
@ -736,27 +763,28 @@ describe('validation logic', () => {
|
|||
"SyntaxError: missing STRING at '%'",
|
||||
]);
|
||||
// Do not try to validate the dissect pattern string
|
||||
testErrorsAndWarnings('from a | dissect stringField "%{a}"', []);
|
||||
testErrorsAndWarnings('from a | dissect numberField "%{a}"', [
|
||||
testErrorsAndWarnings('from a | dissect stringField "%{firstWord}"', []);
|
||||
testErrorsAndWarnings('from a | dissect numberField "%{firstWord}"', [
|
||||
'DISSECT only supports string type values, found [numberField] of type number',
|
||||
]);
|
||||
testErrorsAndWarnings('from a | dissect stringField "%{a}" option ', [
|
||||
testErrorsAndWarnings('from a | dissect stringField "%{firstWord}" option ', [
|
||||
'SyntaxError: expected {ASSIGN} but found "<EOF>"',
|
||||
]);
|
||||
testErrorsAndWarnings('from a | dissect stringField "%{a}" option = ', [
|
||||
testErrorsAndWarnings('from a | dissect stringField "%{firstWord}" option = ', [
|
||||
'SyntaxError: expected {STRING, INTEGER_LITERAL, DECIMAL_LITERAL, FALSE, NULL, PARAM, TRUE, PLUS, MINUS, OPENING_BRACKET} but found "<EOF>"',
|
||||
'Invalid option for DISSECT: [option]',
|
||||
]);
|
||||
testErrorsAndWarnings('from a | dissect stringField "%{a}" option = 1', [
|
||||
testErrorsAndWarnings('from a | dissect stringField "%{firstWord}" option = 1', [
|
||||
'Invalid option for DISSECT: [option]',
|
||||
]);
|
||||
testErrorsAndWarnings('from a | dissect stringField "%{a}" append_separator = "-"', []);
|
||||
testErrorsAndWarnings('from a | dissect stringField "%{a}" ignore_missing = true', [
|
||||
testErrorsAndWarnings('from a | dissect stringField "%{firstWord}" append_separator = "-"', []);
|
||||
testErrorsAndWarnings('from a | dissect stringField "%{firstWord}" ignore_missing = true', [
|
||||
'Invalid option for DISSECT: [ignore_missing]',
|
||||
]);
|
||||
testErrorsAndWarnings('from a | dissect stringField "%{a}" append_separator = true', [
|
||||
testErrorsAndWarnings('from a | dissect stringField "%{firstWord}" append_separator = true', [
|
||||
'Invalid value for DISSECT append_separator: expected a string, but was [true]',
|
||||
]);
|
||||
testErrorsAndWarnings('from a | dissect stringField "%{firstWord}" | keep firstWord', []);
|
||||
// testErrorsAndWarnings('from a | dissect s* "%{a}"', [
|
||||
// 'Using wildcards (*) in dissect is not allowed [s*]',
|
||||
// ]);
|
||||
|
@ -776,10 +804,11 @@ describe('validation logic', () => {
|
|||
]);
|
||||
testErrorsAndWarnings('from a | grok stringField %a', ["SyntaxError: missing STRING at '%'"]);
|
||||
// Do not try to validate the grok pattern string
|
||||
testErrorsAndWarnings('from a | grok stringField "%{a}"', []);
|
||||
testErrorsAndWarnings('from a | grok numberField "%{a}"', [
|
||||
testErrorsAndWarnings('from a | grok stringField "%{firstWord}"', []);
|
||||
testErrorsAndWarnings('from a | grok numberField "%{firstWord}"', [
|
||||
'GROK only supports string type values, found [numberField] of type number',
|
||||
]);
|
||||
testErrorsAndWarnings('from a | grok stringField "%{firstWord}" | keep firstWord', []);
|
||||
// testErrorsAndWarnings('from a | grok s* "%{a}"', [
|
||||
// 'Using wildcards (*) in grok is not allowed [s*]',
|
||||
// ]);
|
||||
|
@ -1047,7 +1076,7 @@ describe('validation logic', () => {
|
|||
}
|
||||
|
||||
for (const { name, alias, signatures, ...defRest } of evalFunctionsDefinitions) {
|
||||
for (const { params, returnType } of signatures) {
|
||||
for (const { params, returnType, infiniteParams, minParams } of signatures) {
|
||||
const fieldMapping = getFieldMapping(params);
|
||||
testErrorsAndWarnings(
|
||||
`from a | eval var = ${
|
||||
|
@ -1128,6 +1157,40 @@ describe('validation logic', () => {
|
|||
}`,
|
||||
expectedErrors
|
||||
);
|
||||
|
||||
if (!infiniteParams && !minParams) {
|
||||
// test that additional args are spotted
|
||||
const fieldMappingWithOneExtraArg = getFieldMapping(params).concat({
|
||||
name: 'extraArg',
|
||||
type: 'number',
|
||||
});
|
||||
// get the expected args from the first signature in case of errors
|
||||
const expectedArgs = signatures[0].params.filter(({ optional }) => !optional).length;
|
||||
const shouldBeExactly = signatures[0].params.length;
|
||||
testErrorsAndWarnings(
|
||||
`from a | eval ${
|
||||
getFunctionSignatures(
|
||||
{
|
||||
name,
|
||||
...defRest,
|
||||
signatures: [{ params: fieldMappingWithOneExtraArg, returnType }],
|
||||
},
|
||||
{ withTypes: false }
|
||||
)[0].declaration
|
||||
}`,
|
||||
[
|
||||
`Error building [${name}]: expects ${
|
||||
shouldBeExactly - expectedArgs === 0 ? 'exactly ' : ''
|
||||
}${
|
||||
expectedArgs === 1
|
||||
? 'one argument'
|
||||
: expectedArgs === 0
|
||||
? '0 arguments'
|
||||
: `${expectedArgs} arguments`
|
||||
}, passed ${fieldMappingWithOneExtraArg.length} instead.`,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// test that wildcard won't work as arg
|
||||
|
@ -1151,6 +1214,37 @@ describe('validation logic', () => {
|
|||
}
|
||||
}
|
||||
}
|
||||
testErrorsAndWarnings(
|
||||
'from a | eval log10(-1)',
|
||||
[],
|
||||
['Log of a negative number results in null: -1']
|
||||
);
|
||||
testErrorsAndWarnings(
|
||||
'from a | eval log(-1)',
|
||||
[],
|
||||
['Log of a negative number results in null: -1']
|
||||
);
|
||||
testErrorsAndWarnings(
|
||||
'from a | eval log(-1, 20)',
|
||||
[],
|
||||
['Log of a negative number results in null: -1']
|
||||
);
|
||||
testErrorsAndWarnings(
|
||||
'from a | eval log(-1, -20)',
|
||||
[],
|
||||
[
|
||||
'Log of a negative number results in null: -1',
|
||||
'Log of a negative number results in null: -20',
|
||||
]
|
||||
);
|
||||
testErrorsAndWarnings(
|
||||
'from a | eval var0 = log(-1, -20)',
|
||||
[],
|
||||
[
|
||||
'Log of a negative number results in null: -1',
|
||||
'Log of a negative number results in null: -20',
|
||||
]
|
||||
);
|
||||
for (const op of ['>', '>=', '<', '<=', '==']) {
|
||||
testErrorsAndWarnings(`from a | eval numberField ${op} 0`, []);
|
||||
testErrorsAndWarnings(`from a | eval NOT numberField ${op} 0`, []);
|
||||
|
@ -1304,9 +1398,11 @@ describe('validation logic', () => {
|
|||
]);
|
||||
testErrorsAndWarnings('from a | stats numberField=', [
|
||||
'SyntaxError: expected {STRING, INTEGER_LITERAL, DECIMAL_LITERAL, FALSE, LP, NOT, NULL, PARAM, TRUE, PLUS, MINUS, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER} but found "<EOF>"',
|
||||
"Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [=] of type [void]",
|
||||
]);
|
||||
testErrorsAndWarnings('from a | stats numberField=5 by ', [
|
||||
'SyntaxError: expected {STRING, INTEGER_LITERAL, DECIMAL_LITERAL, FALSE, LP, NOT, NULL, PARAM, TRUE, PLUS, MINUS, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER} but found "<EOF>"',
|
||||
"Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [=] of type [void]",
|
||||
]);
|
||||
testErrorsAndWarnings('from a | stats avg(numberField) by wrongField', [
|
||||
'Unknown column [wrongField]',
|
||||
|
@ -1347,6 +1443,12 @@ describe('validation logic', () => {
|
|||
'from a | stats avg(numberField), percentile(numberField, 50) BY ipField',
|
||||
[]
|
||||
);
|
||||
for (const op of ['+', '-', '*', '/', '%']) {
|
||||
testErrorsAndWarnings(
|
||||
`from a | stats avg(numberField) ${op} percentile(numberField, 50) BY ipField`,
|
||||
[]
|
||||
);
|
||||
}
|
||||
testErrorsAndWarnings('from a | stats count(* + 1) BY ipField', [
|
||||
'SyntaxError: expected {STRING, INTEGER_LITERAL, DECIMAL_LITERAL, FALSE, LP, NOT, NULL, PARAM, TRUE, PLUS, MINUS, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER} but found "+"',
|
||||
]);
|
||||
|
@ -1359,15 +1461,33 @@ describe('validation logic', () => {
|
|||
testErrorsAndWarnings('from a | stats count(count(*)) BY ipField', [
|
||||
`Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [count(*)] of type [number]`,
|
||||
]);
|
||||
testErrorsAndWarnings('from a | stats numberField + 1', ['STATS does not support function +']);
|
||||
testErrorsAndWarnings('from a | stats numberField + 1', [
|
||||
`Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [+] of type [number]`,
|
||||
]);
|
||||
|
||||
for (const nesting of [1, 2, 3, 4]) {
|
||||
const moreBuiltinWrapping = Array(nesting).fill('+ 1').join('');
|
||||
testErrorsAndWarnings(`from a | stats 5 + avg(numberField) ${moreBuiltinWrapping}`, []);
|
||||
testErrorsAndWarnings(`from a | stats 5 ${moreBuiltinWrapping} + avg(numberField)`, []);
|
||||
testErrorsAndWarnings(`from a | stats 5 ${moreBuiltinWrapping} + numberField`, [
|
||||
"Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [+] of type [number]",
|
||||
]);
|
||||
testErrorsAndWarnings(`from a | stats 5 + numberField ${moreBuiltinWrapping}`, [
|
||||
"Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [+] of type [number]",
|
||||
]);
|
||||
}
|
||||
|
||||
testErrorsAndWarnings('from a | stats 5 + numberField + 1', [
|
||||
"Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [+] of type [number]",
|
||||
]);
|
||||
|
||||
testErrorsAndWarnings('from a | stats numberField + 1 by ipField', [
|
||||
'STATS does not support function +',
|
||||
`Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [+] of type [number]`,
|
||||
]);
|
||||
|
||||
testErrorsAndWarnings(
|
||||
'from a | stats avg(numberField), percentile(numberField, 50) + 1 by ipField',
|
||||
['STATS does not support function +']
|
||||
[]
|
||||
);
|
||||
|
||||
testErrorsAndWarnings('from a | stats count(*)', []);
|
||||
|
@ -1400,6 +1520,52 @@ describe('validation logic', () => {
|
|||
}`,
|
||||
[]
|
||||
);
|
||||
testErrorsAndWarnings(
|
||||
`from a | stats var = round(${
|
||||
getFunctionSignatures(
|
||||
{ name, ...defRest, signatures: [{ params: fieldMapping, returnType }] },
|
||||
{ withTypes: false }
|
||||
)[0].declaration
|
||||
})`,
|
||||
[]
|
||||
);
|
||||
testErrorsAndWarnings(
|
||||
`from a | stats round(${
|
||||
getFunctionSignatures(
|
||||
{ name, ...defRest, signatures: [{ params: fieldMapping, returnType }] },
|
||||
{ withTypes: false }
|
||||
)[0].declaration
|
||||
})`,
|
||||
[]
|
||||
);
|
||||
testErrorsAndWarnings(
|
||||
`from a | stats var = round(${
|
||||
getFunctionSignatures(
|
||||
{ name, ...defRest, signatures: [{ params: fieldMapping, returnType }] },
|
||||
{ withTypes: false }
|
||||
)[0].declaration
|
||||
}) + ${
|
||||
getFunctionSignatures(
|
||||
{ name, ...defRest, signatures: [{ params: fieldMapping, returnType }] },
|
||||
{ withTypes: false }
|
||||
)[0].declaration
|
||||
}`,
|
||||
[]
|
||||
);
|
||||
testErrorsAndWarnings(
|
||||
`from a | stats round(${
|
||||
getFunctionSignatures(
|
||||
{ name, ...defRest, signatures: [{ params: fieldMapping, returnType }] },
|
||||
{ withTypes: false }
|
||||
)[0].declaration
|
||||
}) + ${
|
||||
getFunctionSignatures(
|
||||
{ name, ...defRest, signatures: [{ params: fieldMapping, returnType }] },
|
||||
{ withTypes: false }
|
||||
)[0].declaration
|
||||
}`,
|
||||
[]
|
||||
);
|
||||
|
||||
if (alias) {
|
||||
for (const otherName of alias) {
|
||||
|
@ -1623,55 +1789,50 @@ describe('validation logic', () => {
|
|||
|
||||
describe('enrich', () => {
|
||||
testErrorsAndWarnings(`from a | enrich`, [
|
||||
'SyntaxError: expected {OPENING_BRACKET, ENRICH_POLICY_NAME} but found "<EOF>"',
|
||||
"SyntaxError: missing ENRICH_POLICY_NAME at '<EOF>'",
|
||||
]);
|
||||
testErrorsAndWarnings(`from a | enrich [`, [
|
||||
'SyntaxError: expected {SETTING} but found "<EOF>"',
|
||||
testErrorsAndWarnings(`from a | enrich _`, ['Unknown policy [_]']);
|
||||
testErrorsAndWarnings(`from a | enrich _:`, [
|
||||
"SyntaxError: token recognition error at: ':'",
|
||||
'Unknown policy [_]',
|
||||
]);
|
||||
testErrorsAndWarnings(`from a | enrich [ccq.mode`, [
|
||||
'SyntaxError: expected {COLON} but found "<EOF>"',
|
||||
testErrorsAndWarnings(`from a | enrich _:policy`, [
|
||||
'Unrecognized value [_] for ENRICH, mode needs to be one of [_ANY, _COORDINATOR, _REMOTE]',
|
||||
]);
|
||||
testErrorsAndWarnings(`from a | enrich [ccq.mode:`, [
|
||||
'SyntaxError: expected {SETTING} but found "<EOF>"',
|
||||
testErrorsAndWarnings(`from a | enrich :policy`, [
|
||||
"SyntaxError: token recognition error at: ':'",
|
||||
]);
|
||||
testErrorsAndWarnings(`from a | enrich [ccq.mode:any`, [
|
||||
'SyntaxError: expected {CLOSING_BRACKET} but found "<EOF>"',
|
||||
testErrorsAndWarnings(`from a | enrich any:`, [
|
||||
"SyntaxError: token recognition error at: ':'",
|
||||
'Unknown policy [any]',
|
||||
]);
|
||||
testErrorsAndWarnings(`from a | enrich [ccq.mode:any] `, [
|
||||
"SyntaxError: extraneous input '<EOF>' expecting {OPENING_BRACKET, ENRICH_POLICY_NAME}",
|
||||
testErrorsAndWarnings(`from a | enrich _any:`, [
|
||||
"SyntaxError: token recognition error at: ':'",
|
||||
'Unknown policy [_any]',
|
||||
]);
|
||||
testErrorsAndWarnings(`from a | enrich any:policy`, [
|
||||
'Unrecognized value [any] for ENRICH, mode needs to be one of [_ANY, _COORDINATOR, _REMOTE]',
|
||||
]);
|
||||
testErrorsAndWarnings(`from a | enrich policy `, []);
|
||||
testErrorsAndWarnings(`from a | enrich [ccq.mode:value] policy `, [
|
||||
'Unrecognized value [value], ENRICH [ccq.mode] needs to be one of [ANY, COORDINATOR, REMOTE]',
|
||||
]);
|
||||
for (const value of ['any', 'coordinator', 'remote']) {
|
||||
testErrorsAndWarnings(`from a | enrich [ccq.mode:${value}] policy `, []);
|
||||
testErrorsAndWarnings(`from a | enrich [ccq.mode:${value.toUpperCase()}] policy `, []);
|
||||
testErrorsAndWarnings(`from a | enrich _${value}:policy `, []);
|
||||
testErrorsAndWarnings(`from a | enrich _${value} : policy `, [
|
||||
"SyntaxError: token recognition error at: ':'",
|
||||
"SyntaxError: extraneous input 'policy' expecting <EOF>",
|
||||
`Unknown policy [_${value}]`,
|
||||
]);
|
||||
testErrorsAndWarnings(`from a | enrich _${value}: policy `, [
|
||||
"SyntaxError: token recognition error at: ':'",
|
||||
"SyntaxError: extraneous input 'policy' expecting <EOF>",
|
||||
`Unknown policy [_${value}]`,
|
||||
]);
|
||||
testErrorsAndWarnings(`from a | enrich _${camelCase(value)}:policy `, []);
|
||||
testErrorsAndWarnings(`from a | enrich _${value.toUpperCase()}:policy `, []);
|
||||
}
|
||||
|
||||
testErrorsAndWarnings(`from a | enrich [setting:value policy`, [
|
||||
'SyntaxError: expected {CLOSING_BRACKET} but found "policy"',
|
||||
'Unsupported setting [setting], expected [ccq.mode]',
|
||||
testErrorsAndWarnings(`from a | enrich _unknown:policy`, [
|
||||
'Unrecognized value [_unknown] for ENRICH, mode needs to be one of [_ANY, _COORDINATOR, _REMOTE]',
|
||||
]);
|
||||
|
||||
testErrorsAndWarnings(`from a | enrich [ccq.mode:any policy`, [
|
||||
'SyntaxError: expected {CLOSING_BRACKET} but found "policy"',
|
||||
]);
|
||||
|
||||
testErrorsAndWarnings(`from a | enrich [ccq.mode:any policy`, [
|
||||
'SyntaxError: expected {CLOSING_BRACKET} but found "policy"',
|
||||
]);
|
||||
|
||||
testErrorsAndWarnings(`from a | enrich [setting:value] policy`, [
|
||||
'Unsupported setting [setting], expected [ccq.mode]',
|
||||
]);
|
||||
testErrorsAndWarnings(`from a | enrich [ccq.mode:any] policy[]`, []);
|
||||
|
||||
testErrorsAndWarnings(
|
||||
`from a | enrich [ccq.mode:any][ccq.mode:coordinator] policy[]`,
|
||||
[],
|
||||
['Multiple definition of setting [ccq.mode]. Only last one will be applied.']
|
||||
);
|
||||
testErrorsAndWarnings(`from a | enrich missing-policy `, ['Unknown policy [missing-policy]']);
|
||||
testErrorsAndWarnings(`from a | enrich policy on `, [
|
||||
"SyntaxError: missing {QUOTED_IDENTIFIER, UNQUOTED_ID_PATTERN} at '<EOF>'",
|
||||
|
@ -1749,7 +1910,7 @@ describe('validation logic', () => {
|
|||
expect(callbackMocks.getSources).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it(`should fetch policies if no enrich command is found`, async () => {
|
||||
it(`should not fetch policies if no enrich command is found`, async () => {
|
||||
const callbackMocks = getCallbackMocks();
|
||||
await validateAst(`row a = 1 | eval a`, getAstAndErrors, callbackMocks);
|
||||
expect(callbackMocks.getPolicies).not.toHaveBeenCalled();
|
||||
|
@ -1794,5 +1955,33 @@ describe('validation logic', () => {
|
|||
query: `from enrichIndex1 | keep otherField, yetAnotherField`,
|
||||
});
|
||||
});
|
||||
|
||||
it(`should not crash if no callbacks are available`, async () => {
|
||||
try {
|
||||
await validateAst(
|
||||
`from a | eval b = a | enrich policy | dissect stringField "%{firstWord}"`,
|
||||
getAstAndErrors,
|
||||
{
|
||||
getFieldsFor: undefined,
|
||||
getSources: undefined,
|
||||
getPolicies: undefined,
|
||||
getMetaFields: undefined,
|
||||
}
|
||||
);
|
||||
} catch {
|
||||
fail('Should not throw');
|
||||
}
|
||||
});
|
||||
|
||||
it(`should not crash if no callbacks are passed`, async () => {
|
||||
try {
|
||||
await validateAst(
|
||||
`from a | eval b = a | enrich policy | dissect stringField "%{firstWord}"`,
|
||||
getAstAndErrors
|
||||
);
|
||||
} catch {
|
||||
fail('Should not throw');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,6 +10,7 @@ import uniqBy from 'lodash/uniqBy';
|
|||
import {
|
||||
CommandModeDefinition,
|
||||
CommandOptionsDefinition,
|
||||
FunctionDefinition,
|
||||
SignatureArgType,
|
||||
} from '../definitions/types';
|
||||
import {
|
||||
|
@ -134,8 +135,9 @@ function validateNestedFunctionArg(
|
|||
) {
|
||||
// The isSupported check ensure the definition exists
|
||||
const argFn = getFunctionDefinition(actualArg.name)!;
|
||||
|
||||
if ('noNestingFunctions' in argDef && argDef.noNestingFunctions) {
|
||||
const fnDef = getFunctionDefinition(astFunction.name)!;
|
||||
// no nestying criteria should be enforced only for same type function
|
||||
if ('noNestingFunctions' in argDef && argDef.noNestingFunctions && fnDef.type === argFn.type) {
|
||||
messages.push(
|
||||
getMessageFromId({
|
||||
messageId: 'noNestedArgumentSupport',
|
||||
|
@ -170,51 +172,53 @@ function validateFunctionColumnArg(
|
|||
parentCommand: string
|
||||
) {
|
||||
const messages: ESQLMessage[] = [];
|
||||
if (isColumnItem(actualArg) && actualArg.name) {
|
||||
const { hit: columnCheck, nameHit } = columnExists(actualArg, references);
|
||||
if (!columnCheck) {
|
||||
messages.push(
|
||||
getMessageFromId({
|
||||
messageId: 'unknownColumn',
|
||||
values: {
|
||||
name: actualArg.name,
|
||||
},
|
||||
locations: actualArg.location,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
if (actualArg.name === '*') {
|
||||
// if function does not support wildcards return a specific error
|
||||
if (!('supportsWildcard' in argDef) || !argDef.supportsWildcard) {
|
||||
messages.push(
|
||||
getMessageFromId({
|
||||
messageId: 'noWildcardSupportAsArg',
|
||||
values: {
|
||||
name: astFunction.name,
|
||||
},
|
||||
locations: actualArg.location,
|
||||
})
|
||||
);
|
||||
}
|
||||
// do not validate any further for now, only count() accepts wildcard as args...
|
||||
if (isColumnItem(actualArg)) {
|
||||
if (actualArg.name) {
|
||||
const { hit: columnCheck, nameHit } = columnExists(actualArg, references);
|
||||
if (!columnCheck) {
|
||||
messages.push(
|
||||
getMessageFromId({
|
||||
messageId: 'unknownColumn',
|
||||
values: {
|
||||
name: actualArg.name,
|
||||
},
|
||||
locations: actualArg.location,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
// guaranteed by the check above
|
||||
const columnHit = getColumnHit(nameHit!, references);
|
||||
// check the type of the column hit
|
||||
const typeHit = columnHit!.type;
|
||||
if (!isEqualType(actualArg, argDef, references, parentCommand)) {
|
||||
messages.push(
|
||||
getMessageFromId({
|
||||
messageId: 'wrongArgumentType',
|
||||
values: {
|
||||
name: astFunction.name,
|
||||
argType: argDef.type,
|
||||
value: actualArg.name,
|
||||
givenType: typeHit,
|
||||
},
|
||||
locations: actualArg.location,
|
||||
})
|
||||
);
|
||||
if (actualArg.name === '*') {
|
||||
// if function does not support wildcards return a specific error
|
||||
if (!('supportsWildcard' in argDef) || !argDef.supportsWildcard) {
|
||||
messages.push(
|
||||
getMessageFromId({
|
||||
messageId: 'noWildcardSupportAsArg',
|
||||
values: {
|
||||
name: astFunction.name,
|
||||
},
|
||||
locations: actualArg.location,
|
||||
})
|
||||
);
|
||||
}
|
||||
// do not validate any further for now, only count() accepts wildcard as args...
|
||||
} else {
|
||||
// guaranteed by the check above
|
||||
const columnHit = getColumnHit(nameHit!, references);
|
||||
// check the type of the column hit
|
||||
const typeHit = columnHit!.type;
|
||||
if (!isEqualType(actualArg, argDef, references, parentCommand)) {
|
||||
messages.push(
|
||||
getMessageFromId({
|
||||
messageId: 'wrongArgumentType',
|
||||
values: {
|
||||
name: astFunction.name,
|
||||
argType: argDef.type,
|
||||
value: actualArg.name,
|
||||
givenType: typeHit,
|
||||
},
|
||||
locations: actualArg.location,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -222,6 +226,24 @@ function validateFunctionColumnArg(
|
|||
return messages;
|
||||
}
|
||||
|
||||
function extractCompatibleSignaturesForFunction(
|
||||
fnDef: FunctionDefinition,
|
||||
astFunction: ESQLFunction
|
||||
) {
|
||||
return fnDef.signatures.filter((def) => {
|
||||
if (def.infiniteParams && astFunction.args.length > 0) {
|
||||
return true;
|
||||
}
|
||||
if (def.minParams && astFunction.args.length >= def.minParams) {
|
||||
return true;
|
||||
}
|
||||
if (astFunction.args.length === def.params.length) {
|
||||
return true;
|
||||
}
|
||||
return astFunction.args.length === def.params.filter(({ optional }) => !optional).length;
|
||||
});
|
||||
}
|
||||
|
||||
function validateFunction(
|
||||
astFunction: ESQLFunction,
|
||||
parentCommand: string,
|
||||
|
@ -235,16 +257,8 @@ function validateFunction(
|
|||
return messages;
|
||||
}
|
||||
const fnDefinition = getFunctionDefinition(astFunction.name)!;
|
||||
const supportNestedFunctions =
|
||||
fnDefinition?.signatures.some(({ params }) =>
|
||||
params.some(({ noNestingFunctions }) => !noNestingFunctions)
|
||||
) || true;
|
||||
|
||||
const isFnSupported = isSupportedFunction(
|
||||
astFunction.name,
|
||||
isNested && !supportNestedFunctions ? 'eval' : parentCommand,
|
||||
parentOption
|
||||
);
|
||||
const isFnSupported = isSupportedFunction(astFunction.name, parentCommand, parentOption);
|
||||
|
||||
if (!isFnSupported.supported) {
|
||||
if (isFnSupported.reason === 'unknownFunction') {
|
||||
|
@ -282,18 +296,7 @@ function validateFunction(
|
|||
return messages;
|
||||
}
|
||||
}
|
||||
const matchingSignatures = fnDefinition.signatures.filter((def) => {
|
||||
if (def.infiniteParams && astFunction.args.length > 0) {
|
||||
return true;
|
||||
}
|
||||
if (def.minParams && astFunction.args.length >= def.minParams) {
|
||||
return true;
|
||||
}
|
||||
if (astFunction.args.length === def.params.length) {
|
||||
return true;
|
||||
}
|
||||
return astFunction.args.length >= def.params.filter(({ optional }) => !optional).length;
|
||||
});
|
||||
const matchingSignatures = extractCompatibleSignaturesForFunction(fnDefinition, astFunction);
|
||||
if (!matchingSignatures.length) {
|
||||
const numArgs = fnDefinition.signatures[0].params.filter(({ optional }) => !optional).length;
|
||||
messages.push(
|
||||
|
@ -303,6 +306,7 @@ function validateFunction(
|
|||
fn: astFunction.name,
|
||||
numArgs,
|
||||
passedArgs: astFunction.args.length,
|
||||
exactly: fnDefinition.signatures[0].params.length - numArgs,
|
||||
},
|
||||
locations: astFunction.location,
|
||||
})
|
||||
|
@ -325,9 +329,9 @@ function validateFunction(
|
|||
}
|
||||
}
|
||||
}
|
||||
// check if the definition has some warning to show:
|
||||
if (fnDefinition.warning) {
|
||||
const payloads = fnDefinition.warning(astFunction);
|
||||
// check if the definition has some specific validation to apply:
|
||||
if (fnDefinition.validate) {
|
||||
const payloads = fnDefinition.validate(astFunction);
|
||||
if (payloads.length) {
|
||||
messages.push(...payloads);
|
||||
}
|
||||
|
@ -399,6 +403,7 @@ function validateFunction(
|
|||
failingSignatures.push(failingSignature);
|
||||
}
|
||||
}
|
||||
|
||||
if (failingSignatures.length && failingSignatures.length === matchingSignatures.length) {
|
||||
const failingSignatureOrderedByErrorCount = failingSignatures
|
||||
.map((arr, index) => ({ index, count: arr.length }))
|
||||
|
@ -435,27 +440,27 @@ function validateSetting(
|
|||
);
|
||||
return messages;
|
||||
}
|
||||
setting.args.forEach((arg, index) => {
|
||||
if (!Array.isArray(arg)) {
|
||||
const argDef = settingDef.signature.params[index];
|
||||
const value = 'value' in arg ? arg.value : arg.name;
|
||||
if (argDef.values && !argDef.values?.includes(String(value).toLowerCase())) {
|
||||
messages.push(
|
||||
getMessageFromId({
|
||||
messageId: 'unsupportedSettingCommandValue',
|
||||
values: {
|
||||
setting: setting.name,
|
||||
command: command.name.toUpperCase(),
|
||||
value: String(value),
|
||||
// for some reason all this enums are uppercase in ES
|
||||
expected: (argDef.values?.join(', ') || argDef.type).toUpperCase(),
|
||||
},
|
||||
locations: arg.location,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (
|
||||
settingDef.values.every(({ name }) => name !== setting.name) ||
|
||||
// enforce the check on the prefix if present
|
||||
(settingDef.prefix && !setting.text.startsWith(settingDef.prefix))
|
||||
) {
|
||||
messages.push(
|
||||
getMessageFromId({
|
||||
messageId: 'unsupportedSettingCommandValue',
|
||||
values: {
|
||||
command: command.name.toUpperCase(),
|
||||
value: setting.text,
|
||||
// for some reason all this enums are uppercase in ES
|
||||
expected: settingDef.values
|
||||
.map(({ name }) => `${settingDef.prefix || ''}${name}`)
|
||||
.join(', ')
|
||||
.toUpperCase(),
|
||||
},
|
||||
locations: setting.location,
|
||||
})
|
||||
);
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
|
||||
|
@ -657,14 +662,7 @@ function validateCommand(command: ESQLCommand, references: ReferenceMaps): ESQLM
|
|||
}
|
||||
|
||||
if (isSettingItem(arg)) {
|
||||
messages.push(
|
||||
...validateSetting(
|
||||
arg,
|
||||
commandDef.modes?.find(({ name }) => name === arg.name),
|
||||
command,
|
||||
references
|
||||
)
|
||||
);
|
||||
messages.push(...validateSetting(arg, commandDef.modes[0], command, references));
|
||||
}
|
||||
|
||||
if (isOptionItem(arg)) {
|
||||
|
@ -790,7 +788,7 @@ export async function validateAst(
|
|||
retrieveMetadataFields(callbacks),
|
||||
]);
|
||||
|
||||
if (availablePolicies.size && ast.filter(({ name }) => name === 'enrich')) {
|
||||
if (availablePolicies.size) {
|
||||
const fieldsFromPoliciesMap = await retrievePoliciesFields(ast, availablePolicies, callbacks);
|
||||
fieldsFromPoliciesMap.forEach((value, key) => availableFields.set(key, value));
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue