Introduce simple kibana query language (#15646)

* Introduce simple kuery language

* Rename to kql and add modules
This commit is contained in:
Lukas Olson 2018-01-05 12:54:04 -07:00
parent 303f98a25a
commit 13246b3ea3
13 changed files with 176 additions and 19 deletions

View file

@ -39,7 +39,7 @@
ng-show="showFilterBar()"
state="state"
index-patterns="indexPatterns"
ng-if="model.query.language === 'lucene'"
ng-if="['lucene', 'kql'].includes(model.query.language)"
></filter-bar>
<div

View file

@ -39,7 +39,7 @@
<filter-bar
state="state"
index-patterns="[indexPattern]"
ng-if="state.query.language === 'lucene'"
ng-if="['lucene', 'kql'].includes(state.query.language)"
></filter-bar>
</div>
<div class="row">

View file

@ -45,7 +45,7 @@
<!-- Filters. -->
<filter-bar
ng-if="vis.type.options.showFilterBar && state.query.language === 'lucene'"
ng-if="vis.type.options.showFilterBar && ['lucene', 'kql'].includes(state.query.language)"
state="state"
index-patterns="[indexPattern]"
></filter-bar>

View file

@ -120,7 +120,7 @@ export function CoordinateMapsVisualizationProvider(Notifier, Private) {
const query = this.vis.API.queryManager.getQuery();
const language = query.language;
if (language === 'lucene') {
if (['lucene', 'kql'].includes(language)) {
const filter = { meta: { negate: false, index: indexPatternName } };
filter[filterName] = { ignore_unmapped: true };
filter[filterName][field] = filterData;
@ -198,4 +198,3 @@ export function CoordinateMapsVisualizationProvider(Notifier, Private) {
return CoordinateMapsVisualization;
}

View file

@ -1,6 +1,6 @@
import { groupBy, has } from 'lodash';
import { DecorateQueryProvider } from '../_decorate_query';
import { buildQueryFromKuery } from './from_kuery';
import { buildQueryFromKuery, buildQueryFromKql } from './from_kuery';
import { buildQueryFromFilters } from './from_filters';
import { buildQueryFromLucene } from './from_lucene';
@ -17,15 +17,16 @@ export function BuildESQueryProvider(Private) {
const queriesByLanguage = groupBy(validQueries, 'language');
const kueryQuery = buildQueryFromKuery(indexPattern, queriesByLanguage.kuery);
const kqlQuery = buildQueryFromKql(indexPattern, queriesByLanguage.kql);
const luceneQuery = buildQueryFromLucene(queriesByLanguage.lucene, decorateQuery);
const filterQuery = buildQueryFromFilters(filters, decorateQuery);
return {
bool: {
must: [].concat(kueryQuery.must, luceneQuery.must, filterQuery.must),
filter: [].concat(kueryQuery.filter, luceneQuery.filter, filterQuery.filter),
should: [].concat(kueryQuery.should, luceneQuery.should, filterQuery.should),
must_not: [].concat(kueryQuery.must_not, luceneQuery.must_not, filterQuery.must_not),
must: [].concat(kueryQuery.must, kqlQuery.must, luceneQuery.must, filterQuery.must),
filter: [].concat(kueryQuery.filter, kqlQuery.filter, luceneQuery.filter, filterQuery.filter),
should: [].concat(kueryQuery.should, kqlQuery.should, luceneQuery.should, filterQuery.should),
must_not: [].concat(kueryQuery.must_not, kqlQuery.must_not, luceneQuery.must_not, filterQuery.must_not),
}
};
}

View file

@ -1,8 +1,17 @@
import _ from 'lodash';
import { fromKueryExpression, toElasticsearchQuery, nodeTypes } from '../../../kuery';
import { fromKueryExpression, fromKqlExpression, toElasticsearchQuery, nodeTypes } from '../../../kuery';
export function buildQueryFromKuery(indexPattern, queries) {
const queryASTs = _.map(queries, query => fromKueryExpression(query.query));
return buildQuery(indexPattern, queryASTs);
}
export function buildQueryFromKql(indexPattern, queries) {
const queryASTs = _.map(queries, query => fromKqlExpression(query.query));
return buildQuery(indexPattern, queryASTs);
}
function buildQuery(indexPattern, queryASTs) {
const compoundQueryAST = nodeTypes.function.buildNode('and', queryASTs);
const kueryQuery = toElasticsearchQuery(compoundQueryAST, indexPattern);
return {
@ -13,5 +22,3 @@ export function buildQueryFromKuery(indexPattern, queries) {
...kueryQuery.bool
};
}

View file

@ -8,7 +8,7 @@ export function addFilter(field, values = [], operation, index, state, filterMan
values = [values];
}
if (state.query.language === 'lucene') {
if (['lucene', 'kql'].includes(state.query.language)) {
filterManager.add(field, values, operation, index);
}

View file

@ -64,7 +64,7 @@ export function FilterBarClickHandlerProvider(Notifier, Private) {
filters = dedupFilters($state.filters, uniqFilters(filters), { negate: true });
if (!simulate) {
if ($state.query.language === 'lucene') {
if (['lucene', 'kql'].includes($state.query.language)) {
$state.$newFilters = filters;
}
else if ($state.query.language === 'kuery') {
@ -81,4 +81,3 @@ export function FilterBarClickHandlerProvider(Notifier, Private) {
};
};
}

View file

@ -1,11 +1,21 @@
import grammar from 'raw-loader!./kuery.peg';
import kqlGrammar from 'raw-loader!./kql.peg';
import PEG from 'pegjs';
import _ from 'lodash';
import { nodeTypes } from '../node_types/index';
const kueryParser = PEG.buildParser(grammar);
const kqlParser = PEG.buildParser(kqlGrammar);
export function fromKueryExpression(expression, parseOptions = {}) {
export function fromKueryExpression(expression, parseOptions) {
return fromExpression(expression, parseOptions, kueryParser);
}
export function fromKqlExpression(expression, parseOptions) {
return fromExpression(expression, parseOptions, kqlParser);
}
function fromExpression(expression, parseOptions = {}, parser = kqlParser) {
if (_.isUndefined(expression)) {
throw new Error('expression must be a string, got undefined instead');
}
@ -15,7 +25,7 @@ export function fromKueryExpression(expression, parseOptions = {}) {
helpers: { nodeTypes }
};
return kueryParser.parse(expression, parseOptions);
return parser.parse(expression, parseOptions);
}
export function toKueryExpression(node) {

View file

@ -1 +1 @@
export { fromKueryExpression, toKueryExpression, toElasticsearchQuery } from './ast';
export { fromKueryExpression, fromKqlExpression, toKueryExpression, toElasticsearchQuery } from './ast';

View file

@ -0,0 +1,123 @@
/*
* Kuery parser
*/
/*
* Initialization block
*/
{
var nodeTypes = options.helpers.nodeTypes;
if (options.includeMetadata === undefined) {
options.includeMetadata = true;
}
function addMeta(source, text, location) {
if (options.includeMetadata) {
return Object.assign(
{},
source,
{
text: text,
location: simpleLocation(location),
}
);
}
return source;
}
function simpleLocation(location) {
// Returns an object representing the position of the function within the expression,
// demarcated by the position of its first character and last character. We calculate these values
// using the offset because the expression could span multiple lines, and we don't want to deal
// with column and line values.
return {
min: location.start.offset,
max: location.end.offset
}
}
}
start
= Query
/ space* {
return addMeta(nodeTypes.function.buildNode('and', []), text(), location());
}
Query
= space? query:OrQuery space? {
if (query.type === 'literal') {
return addMeta(nodeTypes.function.buildNode('and', [query]), text(), location());
}
return query;
}
OrQuery
= left:AndQuery space 'or'i space right:OrQuery {
return addMeta(nodeTypes.function.buildNode('or', [left, right]), text(), location());
}
/ AndQuery
AndQuery
= left:NotQuery space 'and'i space right:AndQuery {
return addMeta(nodeTypes.function.buildNode('and', [left, right]), text(), location());
}
/ NotQuery
NotQuery
= 'not'i space clause:Clause {
return addMeta(nodeTypes.function.buildNode('not', clause), text(), location());
}
/ Clause
Clause
= '(' subQuery:Query ')' {
return subQuery;
}
/ Term
Term
= field:literal_arg_type space? ':' space? value:literal_arg_type {
return addMeta(nodeTypes.function.buildNodeWithArgumentNodes('is', [field, value]), text(), location());
}
/ field:literal_arg_type space? ':' space? '[' space? gt:literal_arg_type space 'to'i space lt:literal_arg_type space? ']' {
return addMeta(nodeTypes.function.buildNodeWithArgumentNodes('range', [field, gt, lt]), text(), location());
}
/ !Keywords literal:literal_arg_type { return literal; }
literal_arg_type
= literal:literal {
var result = addMeta(nodeTypes.literal.buildNode(literal), text(), location());
return result;
}
Keywords
= 'or'i / 'and'i / 'not'i
/* ----- Core types ----- */
literal "literal"
= '"' chars:dq_char* '"' { return chars.join(''); } // double quoted string
/ "'" chars:sq_char* "'" { return chars.join(''); } // single quoted string
/ 'true' { return true; } // unquoted literals from here down
/ 'false' { return false; }
/ 'null' { return null; }
/ string:[^\[\]()"',:=\ \t]+ { // this also matches numbers via Number()
var result = string.join('');
// Sort of hacky, but PEG doesn't have backtracking so
// a number rule is hard to read, and performs worse
if (isNaN(Number(result))) return result;
return Number(result)
}
space
= [\ \t\r\n]+
dq_char
= "\\" sequence:('"' / "\\") { return sequence; }
/ [^"] // everything except "
sq_char
= "\\" sequence:("'" / "\\") { return sequence; }
/ [^'] // everything except '

View file

@ -67,6 +67,23 @@
</div>
</div>
<!-- kql input -->
<div class="kuiLocalSearchAssistedInput" ng-if="queryBar.localQuery.language === 'kql'">
<input
ng-model="queryBar.localQuery.query"
input-focus
disable-input-focus="queryBar.disableAutoFocus"
kbn-typeahead-input
placeholder="Search... (e.g. status:200 AND extension:PHP)"
aria-label="Search input"
aria-describedby="discoverKuerySyntaxHint"
type="text"
class="kuiLocalSearchInput"
ng-class="{'kuiLocalSearchInput-isInvalid': queryBarForm.$invalid}"
data-test-subj="queryInput"
/>
</div>
<select
class="kuiLocalSearchSelect"
ng-options="option for option in queryBar.availableQueryLanguages"

View file

@ -1,4 +1,5 @@
export const queryLanguages = [
'lucene',
'kuery',
// 'kql'
];