Skip to content

Commit 28fb892

Browse files
committed
MDEV-30354 Fix JSON escaping in optimizer trace
The expanded_query field in optimizer trace could produce invalid JSON when queries contained special characters. Use json_escape() to properly handle quotes, backslashes and other special characters in the output. Example: Before: "expanded_query": "select length('a') AS `LENGTH("a")`" After: "expanded_query": "select length('a') AS `LENGTH(\"a\")`" The output is now properly escaped and valid JSON while maintaining readability. Added mysql-test/main/opt_trace_json_escape.test to verify the fix. All new code of the whole pull request, including one or several files that are either new files or modified ones, are contributed under the BSD-new license. I am contributing on behalf of my employer Amazon Web Services, Inc.
1 parent e02f4d7 commit 28fb892

File tree

4 files changed

+210
-2
lines changed

4 files changed

+210
-2
lines changed

mysql-test/main/opt_trace.result

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11581,7 +11581,7 @@ SELECT 'a\0' LIMIT 0 {
1158111581
"select_id": 1,
1158211582
"steps": [
1158311583
{
11584-
"expanded_query": "select 'a\0' AS `a\x00` limit 0"
11584+
"expanded_query": "select 'a\\0' AS `a\\x00` limit 0"
1158511585
}
1158611586
]
1158711587
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
# Testing JSON escaping in optimizer trace output
2+
set optimizer_trace="enabled=on";
3+
# Test 1: Simple double quotes
4+
SELECT LENGTH("a");
5+
LENGTH("a")
6+
1
7+
SELECT json_valid(trace) AS valid_json, trace FROM information_schema.optimizer_trace;
8+
valid_json trace
9+
1 {
10+
"steps": [
11+
{
12+
"join_preparation": {
13+
"select_id": 1,
14+
"steps": [
15+
{
16+
"expanded_query": "select octet_length('a') AS `LENGTH(\"a\")`"
17+
}
18+
]
19+
}
20+
},
21+
{
22+
"join_optimization": {
23+
"select_id": 1,
24+
"steps": []
25+
}
26+
}
27+
]
28+
}
29+
# Test 2: Nested quotes
30+
SELECT LENGTH("a\"b");
31+
LENGTH("a\"b")
32+
3
33+
SELECT json_valid(trace) AS valid_json, trace FROM information_schema.optimizer_trace;
34+
valid_json trace
35+
1 {
36+
"steps": [
37+
{
38+
"join_preparation": {
39+
"select_id": 1,
40+
"steps": [
41+
{
42+
"expanded_query": "select octet_length('a\"b') AS `LENGTH(\"a\\\"b\")`"
43+
}
44+
]
45+
}
46+
},
47+
{
48+
"join_optimization": {
49+
"select_id": 1,
50+
"steps": []
51+
}
52+
}
53+
]
54+
}
55+
# Test 3: Special characters with quotes
56+
SELECT LENGTH("a\n\"b\t\"c");
57+
LENGTH("a\n\"b\t\"c")
58+
7
59+
SELECT json_valid(trace) AS valid_json, trace FROM information_schema.optimizer_trace;
60+
valid_json trace
61+
1 {
62+
"steps": [
63+
{
64+
"join_preparation": {
65+
"select_id": 1,
66+
"steps": [
67+
{
68+
"expanded_query": "select octet_length('a\\n\"b\\t\"c') AS `LENGTH(\"a\\n\\\"b\\t\\\"c\")`"
69+
}
70+
]
71+
}
72+
},
73+
{
74+
"join_optimization": {
75+
"select_id": 1,
76+
"steps": []
77+
}
78+
}
79+
]
80+
}
81+
# Test 4: New line
82+
SELECT COLUMN_JSON(COLUMN_CREATE("foo", "New
83+
line" AS CHAR));
84+
COLUMN_JSON(COLUMN_CREATE("foo", "New
85+
line" AS CHAR))
86+
{"foo":"New\u000Aline"}
87+
SELECT json_valid(trace) AS valid_json, trace FROM information_schema.optimizer_trace;
88+
valid_json trace
89+
1 {
90+
"steps": [
91+
{
92+
"join_preparation": {
93+
"select_id": 1,
94+
"steps": [
95+
{
96+
"expanded_query": "select column_json(column_create('foo','New\\nline' AS char charset latin1 collate latin1_swedish_ci )) AS `COLUMN_JSON(COLUMN_CREATE(\"foo\", \"New\nline\" AS CHAR))`"
97+
}
98+
]
99+
}
100+
},
101+
{
102+
"join_optimization": {
103+
"select_id": 1,
104+
"steps": []
105+
}
106+
}
107+
]
108+
}
109+
# Test 5: Backslashes
110+
SELECT LENGTH("a\\b");
111+
LENGTH("a\\b")
112+
3
113+
SELECT json_valid(trace) AS valid_json, trace FROM information_schema.optimizer_trace;
114+
valid_json trace
115+
1 {
116+
"steps": [
117+
{
118+
"join_preparation": {
119+
"select_id": 1,
120+
"steps": [
121+
{
122+
"expanded_query": "select octet_length('a\\\\b') AS `LENGTH(\"a\\\\b\")`"
123+
}
124+
]
125+
}
126+
},
127+
{
128+
"join_optimization": {
129+
"select_id": 1,
130+
"steps": []
131+
}
132+
}
133+
]
134+
}
135+
# Test 6: JSON functions
136+
SELECT JSON_EXTRACT('{"key":"value"}', "$.key");
137+
JSON_EXTRACT('{"key":"value"}', "$.key")
138+
"value"
139+
SELECT json_valid(trace) AS valid_json, trace FROM information_schema.optimizer_trace;
140+
valid_json trace
141+
1 {
142+
"steps": [
143+
{
144+
"join_preparation": {
145+
"select_id": 1,
146+
"steps": [
147+
{
148+
"expanded_query": "select json_extract('{\"key\":\"value\"}','$.key') AS `JSON_EXTRACT('{\"key\":\"value\"}', \"$.key\")`"
149+
}
150+
]
151+
}
152+
},
153+
{
154+
"join_optimization": {
155+
"select_id": 1,
156+
"steps": []
157+
}
158+
}
159+
]
160+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
--echo # Testing JSON escaping in optimizer trace output
2+
3+
#
4+
# MDEV-30354: Fix JSON escaping in optimizer trace output
5+
#
6+
7+
set optimizer_trace="enabled=on";
8+
9+
--echo # Test 1: Simple double quotes
10+
SELECT LENGTH("a");
11+
SELECT json_valid(trace) AS valid_json, trace FROM information_schema.optimizer_trace;
12+
13+
--echo # Test 2: Nested quotes
14+
SELECT LENGTH("a\"b");
15+
SELECT json_valid(trace) AS valid_json, trace FROM information_schema.optimizer_trace;
16+
17+
--echo # Test 3: Special characters with quotes
18+
SELECT LENGTH("a\n\"b\t\"c");
19+
SELECT json_valid(trace) AS valid_json, trace FROM information_schema.optimizer_trace;
20+
21+
--echo # Test 4: New line
22+
SELECT COLUMN_JSON(COLUMN_CREATE("foo", "New
23+
line" AS CHAR));
24+
SELECT json_valid(trace) AS valid_json, trace FROM information_schema.optimizer_trace;
25+
26+
--echo # Test 5: Backslashes
27+
SELECT LENGTH("a\\b");
28+
SELECT json_valid(trace) AS valid_json, trace FROM information_schema.optimizer_trace;
29+
30+
--echo # Test 6: JSON functions
31+
SELECT JSON_EXTRACT('{"key":"value"}', "$.key");
32+
SELECT json_valid(trace) AS valid_json, trace FROM information_schema.optimizer_trace;

sql/opt_trace.cc

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,23 @@ void opt_trace_print_expanded_query(THD *thd, SELECT_LEX *select_lex,
126126
The output is not very pretty lots of back-ticks, the output
127127
is as the one in explain extended , lets try to improved it here.
128128
*/
129-
writer->add("expanded_query", str.c_ptr_safe(), str.length());
129+
130+
StringBuffer<1024> escaped_str(system_charset_info);
131+
escaped_str.alloc(str.length() * 2 + 1);
132+
// Use json_escape function to properly escape the query string
133+
int result = json_escape(str.charset(),
134+
(const uchar*)str.ptr(),
135+
(const uchar*)str.ptr() + str.length(),
136+
&my_charset_utf8mb4_bin,
137+
(uchar*)escaped_str.ptr(),
138+
(uchar*)escaped_str.ptr() + escaped_str.alloced_length());
139+
140+
if (result >= 0) {
141+
escaped_str.length(result);
142+
writer->add("expanded_query", escaped_str.c_ptr_safe(), escaped_str.length());
143+
} else {
144+
writer->add("expanded_query", str.c_ptr_safe(), str.length());
145+
}
130146
}
131147

132148
void opt_trace_disable_if_no_security_context_access(THD *thd)

0 commit comments

Comments
 (0)