Skip to content

fix(elasticsearch): escape Lucene special chars in values only, not field patterns (APIM-13030)#15831

Open
wbabyte wants to merge 1 commit into4.9.xfrom
fix/APIM-13030
Open

fix(elasticsearch): escape Lucene special chars in values only, not field patterns (APIM-13030)#15831
wbabyte wants to merge 1 commit into4.9.xfrom
fix/APIM-13030

Conversation

@wbabyte
Copy link
Copy Markdown
Contributor

@wbabyte wbabyte commented Mar 20, 2026

Summary

  • APIM-13030: "Search by payload" in Analytics/Logs returns no results — regression from APIM-12955 fix
  • createSafeElasticsearchJsonQuery() over-escapes backslashes in the \\*.body wildcard field prefix, making ALL body/payload searches fail silently
  • Fix: move Lucene value escaping into getQuery() where field:value is already parsed — escape only values, preserve \\*.body field pattern

Test plan

  • Body search with plain word returns correct results
  • Body search with hyphen (test-user) returns correct results
  • Body search with @ (user@example.com) returns correct results
  • Body search with $ ($99) returns correct results
  • Body search with parentheses returns correct results
  • Non-body filters (status:200) still work
  • Combined queries (status:200 AND body:term) still work
  • No ES errors in mAPI logs
  • Unit tests pass (LuceneEscapeTest)

🔗 APIM-13030

🤖 Generated with Claude Code

@wbabyte wbabyte requested a review from a team as a code owner March 20, 2026 11:29
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request resolves a critical regression in Elasticsearch query handling that previously caused 'Search by payload' functionality to fail. The issue stemmed from an overly broad application of Lucene special character escaping, which incorrectly modified field patterns. The fix introduces a more precise escaping strategy, ensuring that only the values within a field:value query clause are escaped, while preserving the integrity of field names and patterns. This restores correct search functionality and improves the robustness of Elasticsearch queries.

Highlights

  • Fixes Elasticsearch Query Regression: Addressed APIM-13030, resolving a regression where 'Search by payload' in Analytics/Logs failed to return results due to incorrect Lucene character escaping.
  • Granular Lucene Escaping: Implemented a refined escaping mechanism that applies Lucene special character escaping only to query values, ensuring field patterns like \*.body are preserved and not over-escaped.
  • Refactored Query Building: Modified the getQuery() method to integrate the new value-specific escaping logic, preventing unintended escaping of field names and wildcards.
  • New Helper Methods for Escaping: Introduced a suite of private helper methods (escapeFilterValues, escapeClauseValues, escapeSingleClauseValue, escapeValueForLuceneJson) to modularize and clarify the precise Lucene value escaping process.
  • Enhanced Unit Test Coverage: Expanded the LuceneEscapeTest with new test cases to thoroughly validate the granular escaping behavior, covering various scenarios including special characters, field name preservation, and complex clauses.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the Lucene query string escaping mechanism in ElasticLogRepository to prevent incorrect query parsing. The changes introduce a more granular escaping process that specifically targets the value portions of field:value clauses, preserving field names and patterns. New helper methods like escapeFilterValues, escapeClauseValues, escapeSingleClauseValue, and escapeValueForLuceneJson were added to achieve this, along with expanded unit tests. However, there are still several issues identified: the getQuery method's filter parsing is fragile and can break if a filter value contains a colon; the escapeValueForLuceneJson method only escapes a subset of Lucene's special characters, potentially leading to incorrect query parsing; and the new unit tests are currently validating dead code paths, requiring refactoring to test the live getQuery logic for proper validation and regression prevention.

Comment on lines 155 to 162
.map(filter -> {
final String filterKey = filter[0];
final String escapedValue = escapeValueForLuceneJson(filter[1]);
if ("body".equals(filterKey)) {
return "\\\\*.body" + ":" + filter[1];
return "\\\\*.body" + ":" + escapedValue;
} else {
return filterKey + ":" + filter[1];
return filterKey + ":" + escapedValue;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

critical

This block assumes filter is a two-element array derived from split(":") in the preceding stream operation. This is fragile and will break if a filter value contains a colon (e.g., a URL), causing parts of the value to be lost. A more robust approach is to use indexOf(':') to find the first colon and split the field from the value. A good example of this is in the escapeSingleClauseValue method added in this same PR, although it is currently part of an unused code path.

Comment on lines +305 to +307
private static String escapeValueForLuceneJson(String value) {
return value.replace("\\", "\\\\\\\\").replace("/", "\\\\/").replace("(", "\\\\(").replace(")", "\\\\)");
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

This method only escapes a small subset of Lucene's special characters (\, /, (, )). The full list of special characters is + - && || ! ( ) { } [ ] ^ \" ~ * ? : \ /. Omitting characters like +, -, *, and ? can lead to incorrect query parsing and unexpected search results. For example, a search for a value containing api-key could be misinterpreted. Please consider expanding this method to cover all special characters. Apache Lucene's QueryParser.escape() method provides a reference for a complete implementation.

Comment on lines +89 to +97
private String invokeEscapeFilterValues(String filter) {
try {
Method method = ElasticLogRepository.class.getDeclaredMethod("escapeFilterValues", String.class);
method.setAccessible(true);
return (String) method.invoke(new ElasticLogRepository(), filter);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

This test helper invokes escapeFilterValues, but that method appears to be dead code in ElasticLogRepository. Consequently, the tests using this helper (e.g., compareImplementationWithStandardParser, escapeFilterValues_*) are not validating the production code path, which now runs through the getQuery method. The tests should be refactored to validate the behavior of the live getQuery logic to ensure it is correctly implemented and to prevent future regressions.

Comment on lines +287 to +292
private static String escapeClauseValues(String clause) {
// Handle OR-separated sub-clauses (e.g. "_id:x OR _id:y")
if (clause.contains(" OR ")) {
return stream(clause.split(" OR ")).map(ElasticLogRepository::escapeSingleClauseValue).collect(joining(" OR "));
}
return escapeSingleClauseValue(clause);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we can add a test for this to verify field names survive because of explicit "OR" handling

Copy link
Copy Markdown
Member

@mukul-tyagi08 mukul-tyagi08 left a comment

Choose a reason for hiding this comment

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

A small comment for test case and also the commit has JIRA reference, not sure if that's okay.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants