Skip to content

Conversation

afoucret
Copy link
Contributor

@afoucret afoucret commented Oct 1, 2025

This pull request addresses an inconsistency in the KQL implementation used by ES|QL, where queries on keyword fields were incorrectly case-insensitive by default. This behavior contradicted the KQL documentation and the behavior of KQL in Kibana, which both specify exact, case-sensitive matching for keyword fields.

The change adjusts the KQL parsing context to ensure that matching on keyword fields is case-sensitive, aligning the ES|QL implementation with the expected standard.

Fixes #135772

The KQL implementation used by the ES|QL kql() function incorrectly defaulted to case-insensitive matching for keyword fields. This behavior was inconsistent with the KQL specification and the implementation in Kibana, which both perform exact, case-sensitive matches on keyword fields.

This commit corrects the default behavior to enforce case-sensitivity for keyword fields within ES|QL, aligning it with the documented and expected behavior.

Fixes elastic#135772
@elasticsearchmachine elasticsearchmachine added needs:triage Requires assignment of a team area label v9.2.0 labels Oct 1, 2025
@afoucret afoucret added :Search Relevance/Search Catch all for Search Relevance auto-backport Automatically create backport pull requests when merged >bug and removed needs:triage Requires assignment of a team area label labels Oct 1, 2025
@elasticsearchmachine elasticsearchmachine added the Team:Search Relevance Meta label for the Search Relevance team in Elasticsearch label Oct 1, 2025
@elasticsearchmachine
Copy link
Collaborator

Hi @afoucret, I've created a changelog YAML for you.

@elasticsearchmachine
Copy link
Collaborator

Pinging @elastic/es-search-relevance (Team:Search Relevance)

@afoucret afoucret added v8.19.6 v9.1.6 v9.0.9 v8.18.9 and removed Team:Search Relevance Meta label for the Search Relevance team in Elasticsearch labels Oct 1, 2025
@afoucret afoucret requested a review from carlosdelest October 1, 2025 13:56
@elasticsearchmachine elasticsearchmachine added the Team:Search Relevance Meta label for the Search Relevance team in Elasticsearch label Oct 1, 2025
Copy link
Member

@carlosdelest carlosdelest left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!


for (boolean caseInsensitive : List.of(true, false)) {
KqlQueryBuilder kqlQuery = new KqlQueryBuilder(KEYWORD_FIELD_NAME + ": foo*");
// Check case case_insensitive is true by default
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Check case case_insensitive is true by default
// Check case case_insensitive is false by default


for (boolean caseInsensitive : List.of(true, false)) {
KqlQueryBuilder kqlQuery = new KqlQueryBuilder(KEYWORD_FIELD_NAME + ": foo");
// Check case case_insensitive is true by default
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Check case case_insensitive is true by default
// Check case case_insensitive is false by default


for (boolean caseInsensitive : List.of(true, false)) {
KqlQueryBuilder kqlQuery = new KqlQueryBuilder(KEYWORD_FIELD_NAME + ": foo*");
// Check case case_insensitive is true by default
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Check case case_insensitive is true by default
// Check case case_insensitive is false by default


for (boolean caseInsensitive : List.of(true, false)) {
KqlQueryBuilder kqlQuery = new KqlQueryBuilder(KEYWORD_FIELD_NAME + ": foo");
// Check case case_insensitive is true by default
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Check case case_insensitive is true by default
// Check case case_insensitive is false by default

@elasticsearchmachine
Copy link
Collaborator

💚 Backport successful

Status Branch Result
8.19
9.1
9.0
8.18

afoucret added a commit to afoucret/elasticsearch that referenced this pull request Oct 1, 2025
afoucret added a commit to afoucret/elasticsearch that referenced this pull request Oct 1, 2025
@swg0101
Copy link

swg0101 commented Oct 1, 2025

Despite this being an inconsistent behavior, we actually have been exploiting this behavior in ES|QL to do case-insensitive searches on keywords where we ordinarily would have to resort to other hacks, like doing a regex DSL with case-insensitive set to true, or doing a to_lower()/upper() in ES|QL. That being said, those functions are much slower than the KQL() function.

It may be wise for there to be a toggle on this so users can choose the behavior they want to see. There has also been a long standing ticket asking for a similar behavior, like this: elastic/kibana#55378

@afoucret
Copy link
Contributor Author

afoucret commented Oct 2, 2025

@swg0101 got your point.

I think:

  • the default behavior should be consistent
  • we should expose the feature as an option of the KQL function

I opened an issue that describe exactly this: #135823.

@swg0101
Copy link

swg0101 commented Oct 2, 2025

That sounds great. Perhaps the result can be slightly more user-friendly, especially if there are no other variables that would need to be passed. Perhaps naming the function KQLi/kqli with and passing the case_insensitive: true parameter would be easier?

Just like using the DSL like this would be cumbersome:

{
  "kql": {
    "query": "user.name: student01",
    "case_insensitive": true
  }
}

Perhaps adding a toggle in Classic Discover would make it more friendly, although it got closed as not planned under: elastic/kibana#55378. Also, out of scope of this ticket... :)

@afoucret
Copy link
Contributor Author

afoucret commented Oct 2, 2025

@swg0101 There are probably some improvements that can be done in the UX but I am very reluctant to introduce new functions for things that can accessible through an optional parameter.

My plan for KQL function is to expose the case_insensitive but also to introduce other params that are supported by the query DSL:

  • default field: list of field to use when no field is specified
  • timezone: the timezone to use for date / time queries

Last but mot least, our plan with the KQL query is to allow users to migrate to ES|QL more easily. We do not want to add new features to the KQL language and would prefer that people rewrite their queries using native ES|QL syntax / functions.
If there are gaps, it would be better that we fix it in ES|QL directly instead of calling KQL to the rescue and call a query language inside a query language. My gut feeling is that we won't add new features to KQL once the above named params are implemented.

@swg0101
Copy link

swg0101 commented Oct 2, 2025

That makes sense. I am guessing that as long as autocomplete works well, then it probably wouldn't be that much of a problem.
To your point about migrating from KQL --> ES|QL, I think the top few things that drag me back into KQL are as follows:

  1. Native ES|QL is still relatively weak when dealing with array values. For example, seeing if a particular field, like related.ip contains a value, regardless if it's in a scalar form or a multivalue (array) form. KQL couldn't care less about what type the field is in, and works like a "generic contains" match, versus ES|QL being much more literal in its matching (e.g., == vs : in KQL). This specific limitation also makes matching IPs difficult, such as something like related.ip: 192.168.0.0/16, where related.ip could contain either one IP or multiple IPs, depending on the document(s) being searched.

  2. The absolute imposition of a strict limit with no pagination support, as described in ESQL: Support pagination #100000.

  3. I am guessing this very discussion about case insensitivity would be one as well, as == is strictly case sensitive, and there is no option to change this on the fly without falling back to KQL().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
auto-backport Automatically create backport pull requests when merged >bug :Search Relevance/Search Catch all for Search Relevance Team:Search Relevance Meta label for the Search Relevance team in Elasticsearch v8.18.9 v8.19.6 v9.0.9 v9.1.6 v9.2.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[ES|QL] KQL defaults to case-insensitive matching on keyword fields (inconsistent with docs & Kibana behavior)
4 participants