From 4731808a7d4ff624ef6b32236deaa93f2593829f Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Fri, 13 Jan 2023 12:05:22 -0700 Subject: [PATCH] [KQL] Add case-insensitive config for term/wildcard queries --- .../src/es_query/build_es_query.ts | 1 + .../kbn-es-query/src/es_query/from_kuery.ts | 8 +++- .../src/kuery/functions/is.test.ts | 40 ++++++++++++++++++- .../kbn-es-query/src/kuery/functions/is.ts | 23 ++++++++++- packages/kbn-es-query/src/kuery/types.ts | 6 +++ 5 files changed, 73 insertions(+), 5 deletions(-) diff --git a/packages/kbn-es-query/src/es_query/build_es_query.ts b/packages/kbn-es-query/src/es_query/build_es_query.ts index 497d38a0530cf..9d1b0b1c6b139 100644 --- a/packages/kbn-es-query/src/es_query/build_es_query.ts +++ b/packages/kbn-es-query/src/es_query/build_es_query.ts @@ -67,6 +67,7 @@ export function buildEsQuery( dateFormatTZ: config.dateFormatTZ, filtersInMustClause: config.filtersInMustClause, nestedIgnoreUnmapped: config.nestedIgnoreUnmapped, + caseInsensitive: config.caseInsensitive, } ); const luceneQuery = buildQueryFromLucene( diff --git a/packages/kbn-es-query/src/es_query/from_kuery.ts b/packages/kbn-es-query/src/es_query/from_kuery.ts index 6a1254a6e5037..be29e2952241a 100644 --- a/packages/kbn-es-query/src/es_query/from_kuery.ts +++ b/packages/kbn-es-query/src/es_query/from_kuery.ts @@ -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, }); } diff --git a/packages/kbn-es-query/src/kuery/functions/is.test.ts b/packages/kbn-es-query/src/kuery/functions/is.test.ts index bde7e956f0c43..75974e019efd0 100644 --- a/packages/kbn-es-query/src/kuery/functions/is.test.ts +++ b/packages/kbn-es-query/src/kuery/functions/is.test.ts @@ -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 }, }, }, ], diff --git a/packages/kbn-es-query/src/kuery/functions/is.ts b/packages/kbn-es-query/src/kuery/functions/is.ts index 5493a3a6072e6..95563e6c429f6 100644 --- a/packages/kbn-es-query/src/kuery/functions/is.ts +++ b/packages/kbn-es-query/src/kuery/functions/is.ts @@ -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({ diff --git a/packages/kbn-es-query/src/kuery/types.ts b/packages/kbn-es-query/src/kuery/types.ts index f6175e41832d8..57cc2a52beeee 100644 --- a/packages/kbn-es-query/src/kuery/types.ts +++ b/packages/kbn-es-query/src/kuery/types.ts @@ -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 */