Skip to content

Commit

Permalink
[ES|QL] Add support for command settings (#175114)
Browse files Browse the repository at this point in the history
## Summary

Sync up with elastic/elasticsearch#103949
grammar changes.

Command definitions now have a new `mode` list of argument, with related
`ESQLCommandMode` at AST level.

Validation now accepts settings and validate the specific ENRICH one
(error message is taken from the ES codebase).
Autocomplete doesn't trigger by default on settings, but it only trigger
when user starts to type a setting with the `[` trigger char:


![enrich_modes](https://github.com/elastic/kibana/assets/924948/8882361d-2fc7-44ab-bc8e-3994fc3e733d)

Note that multiple settings are supported, but shadowing will trigger a
warning.

`ccq.mode` value name and descriptions have been taken from the linked
ES PR.

### Checklist

- [x]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [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

---------

Co-authored-by: Stratoula Kalafateli <efstratia.kalafateli@elastic.co>
  • Loading branch information
dej611 and stratoula authored Jan 23, 2024
1 parent 95b8131 commit 9e2caed
Show file tree
Hide file tree
Showing 28 changed files with 2,161 additions and 1,298 deletions.
2 changes: 1 addition & 1 deletion packages/kbn-monaco/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"license": "SSPL-1.0 OR Elastic License 2.0",
"scripts": {
"build:antlr4ts:painless": "../../node_modules/antlr4ts-cli/antlr4ts ./src/painless/antlr/painless_lexer.g4 ./src/painless/antlr/painless_parser.g4 && node ./scripts/fix_generated_antlr.js painless",
"build:antlr4ts:esql": "../../node_modules/antlr4ts-cli/antlr4ts src/esql/antlr/esql_lexer.g4 src/esql/antlr/esql_parser.g4 && node ./scripts/fix_generated_antlr.js esql",
"build:antlr4ts:esql": "../../node_modules/antlr4ts-cli/antlr4ts src/esql/antlr/esql_lexer.g4 src/esql/antlr/esql_parser.g4 && node ./scripts/fix_generated_antlr.js esql && node ./scripts/esql_update_ast_script.js",
"build:antlr4ts": "npm run build:antlr4ts:painless && npm run build:antlr4ts:esql"
}
}
122 changes: 122 additions & 0 deletions packages/kbn-monaco/scripts/esql_update_ast_script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

const { join } = require('path');
const { ESLint } = require('eslint');
const partition = require('lodash/partition');
const { readdirSync, readFileSync, writeFileSync } = require('fs');
const ora = require('ora');
const log = ora('Updating ES|QL AST walker from antlr grammar').start();
/*
* This script will read from the parser file generated by the "build:antlr4ts:esql" task
* and extract all quoted/unquoted tokens to update their ids
* into the "ast_helper" file.
* This prevents the bundle size to increase by ~500 kb ("esql_parser" size).
* This script is run at the end of "build:antlr4ts:esql" task, so no need to call it manually.
*/
async function execute(folder) {
const generatedAntlrFolder = join(__dirname, '..', 'src', folder, 'antlr');

const generatedAntlrFolderContents = readdirSync(generatedAntlrFolder);

const tokenRegex = /public static readonly (?<name>[A-Z_]*(UN)*QUOTED_[A-Z_]+) = (?<value>\d+);/;
const lexerFile = generatedAntlrFolderContents.find((file) => file === 'esql_parser.ts');
const lexerFileRows = readFileSync(join(generatedAntlrFolder, lexerFile), 'utf8')
.toString()
.split('\n');
const tokenList = [];
for (const row of lexerFileRows) {
const match = row.match(tokenRegex);
if (match?.groups) {
tokenList.push(match.groups);
}
}
const [unquotedList, quotedList] = partition(tokenList, ({ name }) => /UNQUOTED/.test(name));

// now all quote/unquoted tokens are registered
// dump them into the ast_helper file
const astHelperFileFolder = join(__dirname, '..', 'src', folder, 'lib', 'ast');
const astHelperFilename = 'ast_helpers.ts';

try {
const astHelperContentRows = readFileSync(join(astHelperFileFolder, astHelperFilename), 'utf8')
.toString()
.split('\n');

const startAutoGeneratedComment = astHelperContentRows.findIndex(
(row) => row === '/* SCRIPT_MARKER_START */'
);
const endAutoGeneratedComment =
astHelperContentRows.findIndex((row) => row === '/* SCRIPT_MARKER_END */') + 1;

const newFunctionsContent = `
/* SCRIPT_MARKER_START */
function getQuotedText(ctx: ParserRuleContext) {
return [
${quotedList.map(({ name, value }) => `${value} /* esql_parser.${name} */`).join(', ')}
]
.map((keyCode) => ctx.tryGetToken(keyCode, 0))
.filter(nonNullable)[0];
}
function getUnquotedText(ctx: ParserRuleContext) {
return [
${unquotedList.map(({ name, value }) => `${value} /* esql_parser.${name} */`).join(', ')}
]
.map((keyCode) => ctx.tryGetToken(keyCode, 0))
.filter(nonNullable)[0];
}
/* SCRIPT_MARKER_END */
`;

const fileContent = astHelperContentRows
.slice(0, startAutoGeneratedComment)
.concat(newFunctionsContent.split('\n'), astHelperContentRows.slice(endAutoGeneratedComment));

const fileContentString = fileContent.join('\n');

const eslint = new ESLint({
fix: true,
overrideConfig: {
parser: '@typescript-eslint/parser',
parserOptions: {
sourceType: 'module',
ecmaVersion: 2018,
},
rules: {
'@kbn/imports/no_unresolvable_imports': 'off',
'prettier/prettier': [
'error',
{
parser: 'typescript',
},
],
},
},
});

const results = await eslint.lintText(fileContentString);

if (results.some(({ messages }) => messages.length > 0)) {
const formatter = await eslint.loadFormatter('stylish');
const resultText = formatter.format(results);
process.exitCode = 1;
return log.fail(resultText);
}

const filePath = join(astHelperFileFolder, astHelperFilename);
writeFileSync(filePath, results[0].output || '', { encoding: 'utf8' });
} catch (err) {
return log.fail(err.message);
}

log.succeed('Updated ES|QL helper from antlr grammar successfully');
}

execute('esql');
45 changes: 37 additions & 8 deletions packages/kbn-monaco/src/esql/antlr/esql_lexer.g4
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,8 @@ EXPR_WS
//
mode FROM_MODE;
FROM_PIPE : PIPE -> type(PIPE), popMode;
FROM_OPENING_BRACKET : OPENING_BRACKET -> type(OPENING_BRACKET), pushMode(FROM_MODE), pushMode(FROM_MODE);
FROM_CLOSING_BRACKET : CLOSING_BRACKET -> type(CLOSING_BRACKET), popMode, popMode;
FROM_OPENING_BRACKET : OPENING_BRACKET -> type(OPENING_BRACKET);
FROM_CLOSING_BRACKET : CLOSING_BRACKET -> type(CLOSING_BRACKET);
FROM_COMMA : COMMA -> type(COMMA);
FROM_ASSIGN : ASSIGN -> type(ASSIGN);

Expand Down Expand Up @@ -228,11 +228,15 @@ fragment UNQUOTED_ID_BODY_WITH_PATTERN
: (LETTER | DIGIT | UNDERSCORE | ASTERISK)
;

PROJECT_UNQUOTED_IDENTIFIER
UNQUOTED_ID_PATTERN
: (LETTER | ASTERISK) UNQUOTED_ID_BODY_WITH_PATTERN*
| (UNDERSCORE | ASPERAND) UNQUOTED_ID_BODY_WITH_PATTERN+
;

PROJECT_UNQUOTED_IDENTIFIER
: UNQUOTED_ID_PATTERN -> type(UNQUOTED_ID_PATTERN)
;

PROJECT_QUOTED_IDENTIFIER
: QUOTED_IDENTIFIER -> type(QUOTED_IDENTIFIER)
;
Expand Down Expand Up @@ -265,7 +269,7 @@ RENAME_QUOTED_IDENTIFIER

// use the unquoted pattern to let the parser invalidate fields with *
RENAME_UNQUOTED_IDENTIFIER
: PROJECT_UNQUOTED_IDENTIFIER -> type(PROJECT_UNQUOTED_IDENTIFIER)
: UNQUOTED_ID_PATTERN -> type(UNQUOTED_ID_PATTERN)
;

RENAME_LINE_COMMENT
Expand All @@ -283,19 +287,28 @@ RENAME_WS
// | ENRICH ON key WITH fields
mode ENRICH_MODE;
ENRICH_PIPE : PIPE -> type(PIPE), popMode;
ENRICH_OPENING_BRACKET : OPENING_BRACKET -> type(OPENING_BRACKET), pushMode(SETTING_MODE);

ON : O N -> pushMode(ENRICH_FIELD_MODE);
WITH : W I T H -> pushMode(ENRICH_FIELD_MODE);

// use the unquoted pattern to let the parser invalidate fields with *
ENRICH_POLICY_UNQUOTED_IDENTIFIER
: FROM_UNQUOTED_IDENTIFIER -> type(FROM_UNQUOTED_IDENTIFIER)
// similar to that of an index
// see https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-create-index.html#indices-create-api-path-params
fragment ENRICH_POLICY_NAME_BODY
: ~[\\/?"<>| ,#\t\r\n:]
;
ENRICH_POLICY_NAME
: (LETTER | DIGIT) ENRICH_POLICY_NAME_BODY*
;
ENRICH_QUOTED_IDENTIFIER
: QUOTED_IDENTIFIER -> type(QUOTED_IDENTIFIER)
;
ENRICH_MODE_UNQUOTED_VALUE
: ENRICH_POLICY_NAME -> type(ENRICH_POLICY_NAME)
;
ENRICH_LINE_COMMENT
: LINE_COMMENT -> channel(HIDDEN)
;
Expand All @@ -318,7 +331,7 @@ ENRICH_FIELD_DOT: DOT -> type(DOT);
ENRICH_FIELD_WITH : WITH -> type(WITH) ;
ENRICH_FIELD_UNQUOTED_IDENTIFIER
: PROJECT_UNQUOTED_IDENTIFIER -> type(PROJECT_UNQUOTED_IDENTIFIER)
: UNQUOTED_ID_PATTERN -> type(UNQUOTED_ID_PATTERN)
;
ENRICH_FIELD_QUOTED_IDENTIFIER
Expand Down Expand Up @@ -383,6 +396,22 @@ SHOW_WS
: WS -> channel(HIDDEN)
;
mode SETTING_MODE;
SETTING_CLOSING_BRACKET : CLOSING_BRACKET -> type(CLOSING_BRACKET), popMode;
COLON : ':';
SETTING
: (ASPERAND | DIGIT| DOT | LETTER | UNDERSCORE)+
;
SETTING_LINE_COMMENT
: LINE_COMMENT -> channel(HIDDEN)
;
SETTTING_MULTILINE_COMMENT
: MULTILINE_COMMENT -> channel(HIDDEN)
;
SETTING_WS
: WS -> channel(HIDDEN)
;
fragment A : [aA]; // match either an 'a' or 'A'
fragment B : [bB];
fragment C : [cC];
Expand Down
29 changes: 26 additions & 3 deletions packages/kbn-monaco/src/esql/antlr/esql_lexer.interp

Large diffs are not rendered by default.

37 changes: 22 additions & 15 deletions packages/kbn-monaco/src/esql/antlr/esql_lexer.tokens
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ FROM_UNQUOTED_IDENTIFIER=71
FROM_LINE_COMMENT=72
FROM_MULTILINE_COMMENT=73
FROM_WS=74
PROJECT_UNQUOTED_IDENTIFIER=75
UNQUOTED_ID_PATTERN=75
PROJECT_LINE_COMMENT=76
PROJECT_MULTILINE_COMMENT=77
PROJECT_WS=78
Expand All @@ -82,20 +82,26 @@ RENAME_MULTILINE_COMMENT=81
RENAME_WS=82
ON=83
WITH=84
ENRICH_LINE_COMMENT=85
ENRICH_MULTILINE_COMMENT=86
ENRICH_WS=87
ENRICH_FIELD_LINE_COMMENT=88
ENRICH_FIELD_MULTILINE_COMMENT=89
ENRICH_FIELD_WS=90
MVEXPAND_LINE_COMMENT=91
MVEXPAND_MULTILINE_COMMENT=92
MVEXPAND_WS=93
INFO=94
FUNCTIONS=95
SHOW_LINE_COMMENT=96
SHOW_MULTILINE_COMMENT=97
SHOW_WS=98
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
'|'=26
'='=33
','=34
Expand All @@ -115,3 +121,4 @@ SHOW_WS=98
'/'=61
'%'=62
']'=64
':'=100
Loading

0 comments on commit 9e2caed

Please sign in to comment.