Skip to content

Commit 96fb2d1

Browse files
authored
[Console] Console with better SQL support (#51446)
* First iteration of PoC for SQL highlighting and better completion * Alternate implementation (no """sql markers) * xLang markers * - Remove 'start-sql' - no flow :c * Revert "- Remove 'start-sql'" This reverts commit 2d585ee. * Revert "xLang markers" This reverts commit f019616. * Revert "xLang markers" and add comment This reverts commit f019616. * Add elasticsearch sql highlight rules * Add links to sources of data * Update colors * Redo built in functions
1 parent bf29db8 commit 96fb2d1

File tree

8 files changed

+209
-20
lines changed

8 files changed

+209
-20
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
import ace from 'brace';
20+
21+
const { TextHighlightRules } = ace.acequire('ace/mode/text_highlight_rules');
22+
const oop = ace.acequire('ace/lib/oop');
23+
24+
export const ElasticsearchSqlHighlightRules = function(this: any) {
25+
// See https://www.elastic.co/guide/en/elasticsearch/reference/current/sql-commands.html
26+
const keywords =
27+
'describe|between|in|like|not|and|or|desc|select|from|where|having|group|by|order' +
28+
'asc|desc|pivot|for|in|as|show|columns|include|frozen|tables|escape|limit|rlike|all|distinct|is';
29+
30+
const builtinConstants = 'true|false';
31+
32+
// See https://www.elastic.co/guide/en/elasticsearch/reference/current/sql-syntax-show-functions.html
33+
const builtinFunctions =
34+
'avg|count|first|first_value|last|last_value|max|min|sum|kurtosis|mad|percentile|percentile_rank|skewness' +
35+
'|stddev_pop|sum_of_squares|var_pop|histogram|case|coalesce|greatest|ifnull|iif|isnull|least|nullif|nvl' +
36+
'|curdate|current_date|current_time|current_timestamp|curtime|dateadd|datediff|datepart|datetrunc|date_add' +
37+
'|date_diff|date_part|date_trunc|day|dayname|dayofmonth|dayofweek|dayofyear|day_name|day_of_month|day_of_week' +
38+
'|day_of_year|dom|dow|doy|hour|hour_of_day|idow|isodayofweek|isodow|isoweek|isoweekofyear|iso_day_of_week|iso_week_of_year' +
39+
'|iw|iwoy|minute|minute_of_day|minute_of_hour|month|monthname|month_name|month_of_year|now|quarter|second|second_of_minute' +
40+
'|timestampadd|timestampdiff|timestamp_add|timestamp_diff|today|week|week_of_year|year|abs|acos|asin|atan|atan2|cbrt' +
41+
'|ceil|ceiling|cos|cosh|cot|degrees|e|exp|expm1|floor|log|log10|mod|pi|power|radians|rand|random|round|sign|signum|sin' +
42+
'|sinh|sqrt|tan|truncate|ascii|bit_length|char|character_length|char_length|concat|insert|lcase|left|length|locate' +
43+
'|ltrim|octet_length|position|repeat|replace|right|rtrim|space|substring|ucase|cast|convert|database|user|st_astext|st_aswkt' +
44+
'|st_distance|st_geometrytype|st_geomfromtext|st_wkttosql|st_x|st_y|st_z|score';
45+
46+
// See https://www.elastic.co/guide/en/elasticsearch/reference/current/sql-data-types.html
47+
const dataTypes =
48+
'null|boolean|byte|short|integer|long|double|float|half_float|scaled_float|keyword|text|binary|date|ip|object|nested|time' +
49+
'|interval_year|interval_month|interval_day|interval_hour|interval_minute|interval_second|interval_year_to_month' +
50+
'inteval_day_to_hour|interval_day_to_minute|interval_day_to_second|interval_hour_to_minute|interval_hour_to_second' +
51+
'interval_minute_to_second|geo_point|geo_shape|shape';
52+
53+
const keywordMapper = this.createKeywordMapper(
54+
{
55+
keyword: [keywords, builtinFunctions, builtinConstants, dataTypes].join('|'),
56+
},
57+
'identifier',
58+
true
59+
);
60+
61+
this.$rules = {
62+
start: [
63+
{
64+
token: 'comment',
65+
regex: '--.*$',
66+
},
67+
{
68+
token: 'comment',
69+
start: '/\\*',
70+
end: '\\*/',
71+
},
72+
{
73+
token: 'string', // " string
74+
regex: '".*?"',
75+
},
76+
{
77+
token: 'constant', // ' string
78+
regex: "'.*?'",
79+
},
80+
{
81+
token: 'string', // ` string (apache drill)
82+
regex: '`.*?`',
83+
},
84+
{
85+
token: 'entity.name.function', // float
86+
regex: '[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b',
87+
},
88+
{
89+
token: keywordMapper,
90+
regex: '[a-zA-Z_$][a-zA-Z0-9_$]*\\b',
91+
},
92+
{
93+
token: 'keyword.operator',
94+
regex: '⇐|<⇒|\\*|\\.|\\:\\:|\\+|\\-|\\/|\\/\\/|%|&|\\^|~|<|>|<=|=>|==|!=|<>|=',
95+
},
96+
{
97+
token: 'paren.lparen',
98+
regex: '[\\(]',
99+
},
100+
{
101+
token: 'paren.rparen',
102+
regex: '[\\)]',
103+
},
104+
{
105+
token: 'text',
106+
regex: '\\s+',
107+
},
108+
],
109+
};
110+
this.normalizeRules();
111+
};
112+
113+
oop.inherits(ElasticsearchSqlHighlightRules, TextHighlightRules);

src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/input_highlight_rules.js

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,33 @@
2020
const ace = require('brace');
2121
import { addToRules } from './x_json_highlight_rules';
2222

23-
const oop = ace.acequire('ace/lib/oop');
24-
const { TextHighlightRules } = ace.acequire('ace/mode/text_highlight_rules');
25-
export function InputHighlightRules() {
26-
function mergeTokens(/* ... */) {
27-
return [].concat.apply([], arguments);
23+
export function addEOL(tokens, reg, nextIfEOL, normalNext) {
24+
if (typeof reg === 'object') {
25+
reg = reg.source;
2826
}
27+
return [
28+
{ token: tokens.concat(['whitespace']), regex: reg + '(\\s*)$', next: nextIfEOL },
29+
{ token: tokens, regex: reg, next: normalNext }
30+
];
31+
}
2932

30-
function addEOL(tokens, reg, nextIfEOL, normalNext) {
31-
if (typeof reg === 'object') {
32-
reg = reg.source;
33-
}
34-
return [
35-
{ token: tokens.concat(['whitespace']), regex: reg + '(\\s*)$', next: nextIfEOL },
36-
{ token: tokens, regex: reg, next: normalNext }
37-
];
38-
}
33+
export function mergeTokens(/* ... */) {
34+
return [].concat.apply([], arguments);
35+
}
36+
37+
const oop = ace.acequire('ace/lib/oop');
38+
const { TextHighlightRules } = ace.acequire('ace/mode/text_highlight_rules');
3939

40+
export function InputHighlightRules() {
4041
// regexp must not have capturing parentheses. Use (?:) instead.
4142
// regexps are ordered -> the first match is used
4243
/*jshint -W015 */
4344
this.$rules = {
45+
'start-sql': [
46+
{ token: 'whitespace', regex: '\\s+' },
47+
{ token: 'paren.lparen', regex: '{', next: 'json-sql', push: true },
48+
{ regex: '', next: 'start' }
49+
],
4450
'start': mergeTokens([
4551
{ 'token': 'warning', 'regex': '#!.*$' },
4652
{ token: 'comment', regex: /^#.*$/ },
@@ -65,6 +71,7 @@ export function InputHighlightRules() {
6571
addEOL(['whitespace'], /(\s+)/, 'start', 'url')
6672
),
6773
'url': mergeTokens(
74+
addEOL(['url.part'], /(_sql)/, 'start-sql', 'url-sql'),
6875
addEOL(['url.part'], /([^?\/,\s]+)/, 'start'),
6976
addEOL(['url.comma'], /(,)/, 'start'),
7077
addEOL(['url.slash'], /(\/)/, 'start'),
@@ -74,7 +81,17 @@ export function InputHighlightRules() {
7481
addEOL(['url.param', 'url.equal', 'url.value'], /([^&=]+)(=)([^&]*)/, 'start'),
7582
addEOL(['url.param'], /([^&=]+)/, 'start'),
7683
addEOL(['url.amp'], /(&)/, 'start')
77-
)
84+
),
85+
'url-sql': mergeTokens(
86+
addEOL(['url.comma'], /(,)/, 'start-sql'),
87+
addEOL(['url.slash'], /(\/)/, 'start-sql'),
88+
addEOL(['url.questionmark'], /(\?)/, 'start-sql', 'urlParams-sql')
89+
),
90+
'urlParams-sql': mergeTokens(
91+
addEOL(['url.param', 'url.equal', 'url.value'], /([^&=]+)(=)([^&]*)/, 'start-sql'),
92+
addEOL(['url.param'], /([^&=]+)/, 'start-sql'),
93+
addEOL(['url.amp'], /(&)/, 'start-sql')
94+
),
7895
};
7996

8097
addToRules(this);

src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/x_json_highlight_rules.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@
1818
*/
1919

2020
const _ = require('lodash');
21-
const ScriptHighlightRules = require('./script_highlight_rules').ScriptHighlightRules;
21+
22+
import { ElasticsearchSqlHighlightRules } from './elasticsearch_sql_highlight_rules';
23+
const { ScriptHighlightRules } = require('./script_highlight_rules');
2224

2325
const jsonRules = function (root) {
2426
root = root ? root : 'json';
2527
const rules = {};
26-
rules[root] = [
28+
const xJsonRules = [
2729
{
2830
token: ['variable', 'whitespace', 'ace.punctuation.colon', 'whitespace', 'punctuation.start_triple_quote'],
2931
regex: '("(?:[^"]*_)?script"|"inline"|"source")(\\s*?)(:)(\\s*?)(""")',
@@ -106,6 +108,16 @@ const jsonRules = function (root) {
106108
regex: '.+?'
107109
}
108110
];
111+
112+
rules[root] = xJsonRules;
113+
rules[root + '-sql'] = [{
114+
token: ['variable', 'whitespace', 'ace.punctuation.colon', 'whitespace', 'punctuation.start_triple_quote'],
115+
regex: '("query")(\\s*?)(:)(\\s*?)(""")',
116+
next: 'sql-start',
117+
merge: false,
118+
push: true
119+
}].concat(xJsonRules);
120+
109121
rules.string_literal = [
110122
{
111123
token: 'punctuation.end_triple_quote',
@@ -127,4 +139,9 @@ export function addToRules(otherRules, embedUnder) {
127139
regex: '"""',
128140
next: 'pop',
129141
}]);
142+
otherRules.embedRules(ElasticsearchSqlHighlightRules, 'sql-', [{
143+
token: 'punctuation.end_triple_quote',
144+
regex: '"""',
145+
next: 'pop',
146+
}]);
130147
}

src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/autocomplete.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -990,7 +990,7 @@ export default function({ coreEditor: editor, parser }: { coreEditor: CoreEditor
990990
score: 0,
991991
context,
992992
};
993-
// we only need out custom insertMatch behavior for the body
993+
// we only need our custom insertMatch behavior for the body
994994
if (context.autoCompleteType === 'body') {
995995
defaults.completer = {
996996
insertMatch() {
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
// @ts-ignore
21+
import { ConstantComponent } from './constant_component';
22+
23+
export class FullRequestComponent extends ConstantComponent {
24+
private readonly name: string;
25+
constructor(name: string, parent: any, private readonly template: string) {
26+
super(name, parent);
27+
this.name = name;
28+
}
29+
30+
getTerms() {
31+
return [{ name: this.name, snippet: this.template }];
32+
}
33+
}

src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/url_pattern_matcher.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import {
2525
SimpleParamComponent,
2626
} from './index';
2727

28+
import { FullRequestComponent } from './full_request_component';
29+
2830
/**
2931
* @param parametrizedComponentFactories a dict of the following structure
3032
* that will be used as a fall back for pattern parameters (i.e.: {indices})
@@ -54,6 +56,9 @@ export class UrlPatternMatcher {
5456
endpoint.methods.forEach((method) => {
5557
let c;
5658
let activeComponent = this[method].rootComponent;
59+
if (endpoint.template) {
60+
new FullRequestComponent(pattern + '[body]', activeComponent, endpoint.template);
61+
}
5762
const endpointComponents = endpoint.url_components || {};
5863
const partList = pattern.split('/');
5964
_.each(

src/legacy/core_plugins/console/public/np_ready/lib/row_parser.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ export default class RowParser {
4444
return MODE.BETWEEN_REQUESTS;
4545
} // shouldn't really happen
4646

47-
if (mode !== 'start') {
47+
// If another "start" mode is added here because we want to allow for new language highlighting
48+
// please see https://github.com/elastic/kibana/pull/51446 for a discussion on why
49+
// should consider a different approach.
50+
if (mode !== 'start' && mode !== 'start-sql') {
4851
return MODE.IN_REQUEST;
4952
}
5053
let line = (this.editor.getLineValue(lineNumber) || '').trim();

x-pack/legacy/plugins/console_extensions/spec/overrides/sql.query.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"cbor",
1919
"smile"
2020
]
21-
}
21+
},
22+
"template": "_sql?format=json\n{\n \"query\": \"\"\"\n SELECT * FROM \"${1:TABLE}\"\n \"\"\"\n}\n"
2223
}
2324
}

0 commit comments

Comments
 (0)