fix(mysql): prevent crash when DELIMITER follows non-ASCII comments (#360)
parse_delimiter_command used str slicing (line[..10]) which panics on non-char boundaries when the buffer contains multi-byte UTF-8 characters. Also fixed DELIMITER detection to check only the last line of the buffer instead of the entire accumulated content.
This commit is contained in:
1 parent
222ebefa6a
commit
fe6fead2dc
1 file changed
+60
-4
@@ -182,8 +182,17 @@ impl SqlStatementSplitter {
|
|||||||
|
|
||||||
if !self.in_single_quote && !self.in_double_quote && !self.in_backtick && self.dollar_quote_tag.is_none() {
|
if !self.in_single_quote && !self.in_double_quote && !self.in_backtick && self.dollar_quote_tag.is_none() {
|
||||||
if ch == '\n' {
|
if ch == '\n' {
|
||||||
if let Some(new_delim) = parse_delimiter_command(self.buffer.trim()) {
|
let buf_end = self.buffer.len() - 1;
|
||||||
|
let last_line_start = self.buffer[..buf_end].rfind('\n').map_or(0, |p| p + 1);
|
||||||
|
let last_line = self.buffer[last_line_start..buf_end].trim();
|
||||||
|
if let Some(new_delim) = parse_delimiter_command(last_line) {
|
||||||
self.custom_delimiter = if new_delim == ";" { None } else { Some(new_delim.to_string()) };
|
self.custom_delimiter = if new_delim == ";" { None } else { Some(new_delim.to_string()) };
|
||||||
|
if last_line_start > 0 {
|
||||||
|
let before = self.buffer[..last_line_start].trim();
|
||||||
|
if has_executable_sql(before) {
|
||||||
|
statements.push(before.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
self.buffer.clear();
|
self.buffer.clear();
|
||||||
self.previous = None;
|
self.previous = None;
|
||||||
i += 1;
|
i += 1;
|
||||||
@@ -207,7 +216,13 @@ impl SqlStatementSplitter {
|
|||||||
|
|
||||||
pub fn finish(mut self) -> Vec<String> {
|
pub fn finish(mut self) -> Vec<String> {
|
||||||
let mut statements = Vec::new();
|
let mut statements = Vec::new();
|
||||||
if parse_delimiter_command(self.buffer.trim()).is_some() {
|
let trimmed = self.buffer.trim();
|
||||||
|
let last_line = trimmed.rsplit('\n').next().unwrap_or(trimmed).trim();
|
||||||
|
if parse_delimiter_command(last_line).is_some() {
|
||||||
|
let before = trimmed.rsplitn(2, '\n').nth(1).unwrap_or("").trim();
|
||||||
|
if has_executable_sql(before) {
|
||||||
|
statements.push(before.to_string());
|
||||||
|
}
|
||||||
self.buffer.clear();
|
self.buffer.clear();
|
||||||
} else if let Some(ref delim) = self.custom_delimiter {
|
} else if let Some(ref delim) = self.custom_delimiter {
|
||||||
if self.buffer.ends_with(delim.as_str()) {
|
if self.buffer.ends_with(delim.as_str()) {
|
||||||
@@ -283,9 +298,10 @@ pub fn split_sql_batches(sql: &str) -> Vec<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn parse_delimiter_command(line: &str) -> Option<&str> {
|
fn parse_delimiter_command(line: &str) -> Option<&str> {
|
||||||
let rest = if line.len() > 10 && line[..10].eq_ignore_ascii_case("delimiter ") {
|
let bytes = line.as_bytes();
|
||||||
|
let rest = if bytes.len() > 10 && bytes[..10].eq_ignore_ascii_case(b"delimiter ") {
|
||||||
Some(&line[10..])
|
Some(&line[10..])
|
||||||
} else if line.len() > 10 && line[..10].eq_ignore_ascii_case("delimiter\t") {
|
} else if bytes.len() > 10 && bytes[..10].eq_ignore_ascii_case(b"delimiter\t") {
|
||||||
Some(&line[10..])
|
Some(&line[10..])
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -916,4 +932,44 @@ DELIMITER ;";
|
|||||||
vec!["CREATE PROCEDURE p1() BEGIN SELECT 1; END", "CREATE PROCEDURE p2() BEGIN SELECT 2; END",]
|
vec!["CREATE PROCEDURE p1() BEGIN SELECT 1; END", "CREATE PROCEDURE p2() BEGIN SELECT 2; END",]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn delimiter_after_comment_with_chinese() {
|
||||||
|
let sql = "\
|
||||||
|
-- 判断字段是否存在
|
||||||
|
DELIMITER $$
|
||||||
|
DROP FUNCTION IF EXISTS isFieldExisting $$
|
||||||
|
CREATE FUNCTION isFieldExisting(s VARCHAR(100), t VARCHAR(100), f VARCHAR(100))
|
||||||
|
RETURNS INT
|
||||||
|
RETURN (SELECT COUNT(COLUMN_NAME)
|
||||||
|
FROM INFORMATION_SCHEMA.columns
|
||||||
|
WHERE TABLE_SCHEMA = s
|
||||||
|
AND TABLE_NAME = t
|
||||||
|
AND COLUMN_NAME = f)$$
|
||||||
|
DELIMITER ;";
|
||||||
|
let stmts = super::split_sql_statements(sql);
|
||||||
|
assert_eq!(stmts.len(), 2);
|
||||||
|
assert!(stmts[0].starts_with("DROP FUNCTION"));
|
||||||
|
assert!(stmts[1].starts_with("CREATE FUNCTION"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn delimiter_after_ascii_comment() {
|
||||||
|
let sql = "\
|
||||||
|
-- check field existence
|
||||||
|
DELIMITER $$
|
||||||
|
SELECT 1 $$
|
||||||
|
DELIMITER ;";
|
||||||
|
assert_eq!(super::split_sql_statements(sql), vec!["SELECT 1"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn delimiter_after_statement() {
|
||||||
|
let sql = "\
|
||||||
|
SELECT 1;
|
||||||
|
DELIMITER $$
|
||||||
|
SELECT 2 $$
|
||||||
|
DELIMITER ;";
|
||||||
|
assert_eq!(super::split_sql_statements(sql), vec!["SELECT 1", "SELECT 2"]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user