From 764314870867a74c9963ecb78dd99d26434f45ff Mon Sep 17 00:00:00 2001 From: bigliangzao <33975524+bigliangzao@users.noreply.github.com> Date: Wed, 17 Jun 2026 19:00:05 +0800 Subject: [PATCH] fix(mysql): prevent panic on Chinese characters in WHERE clause MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix UTF-8 boundary issues in mysql_top_level_limit, parse_usize_token, and mysql_keyword_at functions that caused crashes when SQL contains Chinese characters in string literals. The issue was introduced in commit 021e1682 which added byte-level indexing without proper UTF-8 boundary checks. When parsing SQL with Chinese text (e.g., \"WHERE name LIKE '%批量保存%'\"), the byte index could point to the middle of a multi-byte UTF-8 character, causing Rust to panic when slicing the string. Changes: - parse_usize_token: Use std::str::from_utf8 to validate slice before parsing - mysql_keyword_at: Add is_char_boundary checks for safe string slicing - mysql_top_level_limit: Skip non-UTF-8-boundary bytes after incrementing index This ensures all byte-level indexing operations stay on valid UTF-8 boundaries, preventing panics while maintaining the original functionality of detecting LIMIT clauses for bounded result set collection. Fixes crash reported by users when executing queries with Chinese conditions. --- crates/dbx-core/src/db/mysql.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/crates/dbx-core/src/db/mysql.rs b/crates/dbx-core/src/db/mysql.rs index 7e5e5989a3..428959072c 100644 --- a/crates/dbx-core/src/db/mysql.rs +++ b/crates/dbx-core/src/db/mysql.rs @@ -1468,7 +1468,11 @@ fn mysql_top_level_limit(sql: &str) -> Option { if depth == 0 && mysql_keyword_at(sql, i, "LIMIT") { return parse_mysql_limit_value(sql, i + "LIMIT".len()); } + // Move to next byte, but ensure we stay on a UTF-8 boundary i += 1; + while i < bytes.len() && !sql.is_char_boundary(i) { + i += 1; + } } None @@ -1497,13 +1501,20 @@ fn parse_usize_token(sql: &str, i: &mut usize) -> Option { if *i == start { return None; } - sql[start..*i].parse().ok() + // Ensure the slice is valid UTF-8 before parsing + std::str::from_utf8(&bytes[start..*i]).ok()?.parse().ok() } fn mysql_keyword_at(sql: &str, i: usize, keyword: &str) -> bool { let end = i + keyword.len(); - end <= sql.len() - && sql[i..end].eq_ignore_ascii_case(keyword) + if end > sql.len() { + return false; + } + // Ensure indices are on UTF-8 boundaries before slicing + if !sql.is_char_boundary(i) || !sql.is_char_boundary(end) { + return false; + } + sql[i..end].eq_ignore_ascii_case(keyword) && (i == 0 || !is_mysql_identifier_byte(sql.as_bytes()[i - 1])) && (end == sql.len() || !is_mysql_identifier_byte(sql.as_bytes()[end])) }