[KQL] Add case-insensitive config for term/wildcard queries (#148916)

## Summary

Part of https://github.com/elastic/kibana/issues/55378.

Adds a `caseInsensitive` option to the KQL parsing options
(`KueryQueryOptions`). When enabled, keyword queries (both
wildcard<sup>[1]</sup> and term<sup>[2]</sup>) will add the
`case_insensitive` option to enable case-insensitive search.

[1]
https://www.elastic.co/guide/en/elasticsearch/reference/8.6/query-dsl-wildcard-query.html#wildcard-query-field-params
[2]
https://www.elastic.co/guide/en/elasticsearch/reference/8.6/query-dsl-term-query.html#term-field-params


### Checklist

- [ ]
[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: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Lukas Olson 2023-01-19 17:27:02 -07:00 committed by GitHub
parent ece7b8ffdf
commit 81d576454e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 73 additions and 5 deletions

View file

@ -67,6 +67,7 @@ export function buildEsQuery(
dateFormatTZ: config.dateFormatTZ,
filtersInMustClause: config.filtersInMustClause,
nestedIgnoreUnmapped: config.nestedIgnoreUnmapped,
caseInsensitive: config.caseInsensitive,
}
);
const luceneQuery = buildQueryFromLucene(

View file

@ -23,7 +23,12 @@ export function buildQueryFromKuery(
{ allowLeadingWildcards = false }: { allowLeadingWildcards?: boolean } = {
allowLeadingWildcards: false,
},
{ filtersInMustClause = false, dateFormatTZ, nestedIgnoreUnmapped }: KueryQueryOptions = {
{
filtersInMustClause = false,
dateFormatTZ,
nestedIgnoreUnmapped,
caseInsensitive,
}: KueryQueryOptions = {
filtersInMustClause: false,
}
): BoolQuery {
@ -35,6 +40,7 @@ export function buildQueryFromKuery(
filtersInMustClause,
dateFormatTZ,
nestedIgnoreUnmapped,
caseInsensitive,
});
}

View file

@ -212,7 +212,7 @@ describe('kuery functions', () => {
should: [
{
wildcard: {
'machine.os.keyword': 'win*',
'machine.os.keyword': { value: 'win*' },
},
},
],
@ -225,6 +225,25 @@ describe('kuery functions', () => {
expect(result).toEqual(expected);
});
test('should create a case-insensitive wildcard query for keyword fields', () => {
const expected = {
bool: {
should: [
{
wildcard: {
'machine.os.keyword': { value: 'win*', case_insensitive: true },
},
},
],
minimum_should_match: 1,
},
};
const node = nodeTypes.function.buildNode('is', 'machine.os.keyword', 'win*');
const result = is.toElasticsearchQuery(node, indexPattern, { caseInsensitive: true });
expect(result).toEqual(expected);
});
test('should support scripted fields', () => {
const node = nodeTypes.function.buildNode('is', 'script string', 'foo');
const result = is.toElasticsearchQuery(node, indexPattern);
@ -370,7 +389,24 @@ describe('kuery functions', () => {
should: [
{
term: {
'machine.os.keyword': 'Win 7',
'machine.os.keyword': { value: 'Win 7' },
},
},
],
minimum_should_match: 1,
},
});
});
test('should use a case-insensitive term query for keyword fields', () => {
const node = nodeTypes.function.buildNode('is', 'machine.os.keyword', 'Win 7');
const result = is.toElasticsearchQuery(node, indexPattern, { caseInsensitive: true });
expect(result).toEqual({
bool: {
should: [
{
term: {
'machine.os.keyword': { value: 'Win 7', case_insensitive: true },
},
},
],

View file

@ -146,7 +146,12 @@ export function toElasticsearchQuery(
const query = isKeywordField
? {
wildcard: {
[field.name]: value,
[field.name]: {
value,
...(typeof config.caseInsensitive === 'boolean' && {
case_insensitive: config.caseInsensitive,
}),
},
},
}
: {
@ -177,8 +182,22 @@ export function toElasticsearchQuery(
},
}),
];
} else if (isKeywordField) {
return [
...accumulator,
wrapWithNestedQuery({
term: {
[field.name]: {
value,
...(typeof config.caseInsensitive === 'boolean' && {
case_insensitive: config.caseInsensitive,
}),
},
},
}),
];
} else {
const queryType = isKeywordField ? 'term' : type === 'phrase' ? 'match_phrase' : 'match';
const queryType = type === 'phrase' ? 'match_phrase' : 'match';
return [
...accumulator,
wrapWithNestedQuery({

View file

@ -47,6 +47,12 @@ export interface KueryQueryOptions {
* The `nestedIgnoreUnmapped` param allows creating queries with "ignore_unmapped": true
*/
nestedIgnoreUnmapped?: boolean;
/**
* Whether term-level queries should be treated as case-insensitive or not. For example, `agent.keyword: foobar` won't
* match a value of "FooBar" unless this parameter is `true`.
* (See https://www.elastic.co/guide/en/elasticsearch/reference/8.6/query-dsl-term-query.html#term-field-params)
*/
caseInsensitive?: boolean;
}
/** @public */