Skip to content

Commit 8bd3362

Browse files
authored
[Discover][ESQL] Fix filtering by multiline string fields (elastic#250047)
## Summary This PR aims to fix a bug related with adding filters to ESQL queries for multiline string fields. This was first detected when working with the Log overview in the Discover flyout. An error/exception message that contained line breaks caused the generated query, when applying "Filter for value" on the content breakdown, to be invalid in ESQL. Closes elastic#243347 ### Problem When filtering by a field value (e.g., exception.message) that contains line breaks in ESQL mode, the generated query was invalid because the `escapeStringValue` function did not escape newline characters. The literal newlines were inserted directly into the query string, breaking ESQL syntax. ### Changes - Updated `escapeStringValue` in `kbn-esql-utils` to escape newlines (\n), carriage returns (\r) and tabs (\t) - Added unit tests covering line breaks, carriage returns, tabs and combined special characters ### Demo #### Before https://github.com/user-attachments/assets/5e80c9e3-6c60-4643-9fa6-adbce1b63645 #### After https://github.com/user-attachments/assets/aaeb8818-74be-4987-bdcb-321a6269fb9a
1 parent d2efaab commit 8bd3362

3 files changed

Lines changed: 94 additions & 3 deletions

File tree

src/platform/packages/shared/kbn-esql-utils/src/utils/append_to_query/append_where.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,4 +253,60 @@ AND MATCH(\`tags.keyword\`, "info") AND MATCH(\`tags.keyword\`, "success")`
253253
| WHERE \`tags.keyword\` is null`
254254
);
255255
});
256+
257+
it('properly escapes values containing line breaks', () => {
258+
const messageWithLineBreaks = `Error occurred at line 10\nStack trace:\n at function foo()`;
259+
260+
expect(
261+
appendWhereClauseToESQLQuery(
262+
'from logs-*',
263+
'exception.message',
264+
messageWithLineBreaks,
265+
'+',
266+
'string'
267+
)
268+
).toBe(
269+
`from logs-*
270+
| WHERE \`exception.message\` == "Error occurred at line 10\\nStack trace:\\n at function foo()"`
271+
);
272+
});
273+
274+
it('properly escapes values containing carriage returns and tabs', () => {
275+
const messageWithReturnsAndTabs = 'Error\r\nwith\ttabs';
276+
277+
expect(
278+
appendWhereClauseToESQLQuery(
279+
'from logs-*',
280+
'message',
281+
messageWithReturnsAndTabs,
282+
'+',
283+
'string'
284+
)
285+
).toBe(
286+
`from logs-*
287+
| WHERE \`message\` == "Error\\r\\nwith\\ttabs"`
288+
);
289+
});
290+
291+
it('properly escapes line breaks in multivalue MATCH clauses', () => {
292+
const valuesWithLineBreaks = ['error\nmessage', 'another\nvalue'];
293+
294+
expect(
295+
appendWhereClauseToESQLQuery('from logs-*', 'message', valuesWithLineBreaks, '+', 'string')
296+
).toBe(
297+
`from logs-*
298+
| WHERE MATCH(\`message\`, "error\\nmessage") AND MATCH(\`message\`, "another\\nvalue")`
299+
);
300+
});
301+
302+
it('properly escapes all possible special characters in a string value', () => {
303+
const complexValue = 'Error: "path\\to\\file"\nStack trace\r\nwith\ttabs';
304+
305+
expect(
306+
appendWhereClauseToESQLQuery('from logs-*', 'message', complexValue, '+', 'string')
307+
).toBe(
308+
`from logs-*
309+
| WHERE \`message\` == "Error: \\"path\\\\to\\\\file\\"\\nStack trace\\r\\nwith\\ttabs"`
310+
);
311+
});
256312
});

src/platform/packages/shared/kbn-esql-utils/src/utils/append_to_query/utils.test.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* License v3.0 only", or the "Server Side Public License, v 1".
88
*/
99

10-
import { appendToESQLQuery } from './utils';
10+
import { appendToESQLQuery, escapeStringValue } from './utils';
1111

1212
describe('appendToESQLQuery', () => {
1313
it('append the text on a new line after the query', () => {
@@ -25,3 +25,33 @@ describe('appendToESQLQuery', () => {
2525
);
2626
});
2727
});
28+
29+
describe('escapeStringValue', () => {
30+
it('wraps value in double quotes', () => {
31+
expect(escapeStringValue('hello')).toBe('"hello"');
32+
});
33+
34+
it('escapes backslashes', () => {
35+
expect(escapeStringValue('path\\to\\file')).toBe('"path\\\\to\\\\file"');
36+
});
37+
38+
it('escapes double quotes', () => {
39+
expect(escapeStringValue('say "hello"')).toBe('"say \\"hello\\""');
40+
});
41+
42+
it('escapes newlines', () => {
43+
expect(escapeStringValue('line1\nline2')).toBe('"line1\\nline2"');
44+
});
45+
46+
it('escapes carriage returns', () => {
47+
expect(escapeStringValue('line1\rline2')).toBe('"line1\\rline2"');
48+
});
49+
50+
it('escapes tabs', () => {
51+
expect(escapeStringValue('col1\tcol2')).toBe('"col1\\tcol2"');
52+
});
53+
54+
it('handles all special characters combined', () => {
55+
expect(escapeStringValue('a\\b"c\nd\re\tf')).toBe('"a\\\\b\\"c\\nd\\re\\tf"');
56+
});
57+
});

src/platform/packages/shared/kbn-esql-utils/src/utils/append_to_query/utils.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,15 @@ export function getSupportedOperators(): SupportedOperators[] {
7070
}
7171

7272
/**
73-
* Escapes a string value for use in ES|QL queries by escaping backslashes and quotes
73+
* Escapes a string value for use in ES|QL queries by escaping special characters
7474
*/
7575
export function escapeStringValue(val: string): string {
76-
return `"${val.replace(/\\/g, '\\\\').replace(/\"/g, '\\"')}"`;
76+
return `"${val
77+
.replace(/\\/g, '\\\\')
78+
.replace(/\"/g, '\\"')
79+
.replace(/\n/g, '\\n')
80+
.replace(/\r/g, '\\r')
81+
.replace(/\t/g, '\\t')}"`;
7782
}
7883

7984
/**

0 commit comments

Comments
 (0)