Skip to content

Commit 04d9f3a

Browse files
authored
Added support for Mysql Backslash escapes (enabled by default) (#844)
1 parent 00d0712 commit 04d9f3a

File tree

2 files changed

+42
-32
lines changed

2 files changed

+42
-32
lines changed

src/tokenizer.rs

+19-8
Original file line numberDiff line numberDiff line change
@@ -1094,29 +1094,40 @@ impl<'a> Tokenizer<'a> {
10941094

10951095
chars.next(); // consume the opening quote
10961096

1097-
// slash escaping is specific to MySQL dialect
1098-
let mut is_escaped = false;
10991097
while let Some(&ch) = chars.peek() {
11001098
match ch {
11011099
char if char == quote_style => {
11021100
chars.next(); // consume
1103-
if is_escaped {
1104-
s.push(ch);
1105-
is_escaped = false;
1106-
} else if chars.peek().map(|c| *c == quote_style).unwrap_or(false) {
1101+
if chars.peek().map(|c| *c == quote_style).unwrap_or(false) {
11071102
s.push(ch);
11081103
chars.next();
11091104
} else {
11101105
return Ok(s);
11111106
}
11121107
}
11131108
'\\' => {
1109+
// consume
1110+
chars.next();
1111+
// slash escaping is specific to MySQL dialect
11141112
if dialect_of!(self is MySqlDialect) {
1115-
is_escaped = !is_escaped;
1113+
if let Some(next) = chars.peek() {
1114+
// See https://dev.mysql.com/doc/refman/8.0/en/string-literals.html#character-escape-sequences
1115+
let n = match next {
1116+
'\'' | '\"' | '\\' | '%' | '_' => *next,
1117+
'0' => '\0',
1118+
'b' => '\u{8}',
1119+
'n' => '\n',
1120+
'r' => '\r',
1121+
't' => '\t',
1122+
'Z' => '\u{1a}',
1123+
_ => *next,
1124+
};
1125+
s.push(n);
1126+
chars.next(); // consume next
1127+
}
11161128
} else {
11171129
s.push(ch);
11181130
}
1119-
chars.next();
11201131
}
11211132
_ => {
11221133
chars.next(); // consume

tests/sqlparser_mysql.rs

+23-24
Original file line numberDiff line numberDiff line change
@@ -518,35 +518,34 @@ fn parse_unterminated_escape() {
518518

519519
#[test]
520520
fn parse_escaped_string() {
521-
let sql = r#"SELECT 'I\'m fine'"#;
522-
523-
let stmt = mysql().one_statement_parses_to(sql, "");
524-
525-
match stmt {
526-
Statement::Query(query) => match *query.body {
527-
SetExpr::Select(value) => {
528-
let expr = expr_from_projection(only(&value.projection));
529-
assert_eq!(
530-
*expr,
531-
Expr::Value(Value::SingleQuotedString("I'm fine".to_string()))
532-
);
533-
}
521+
fn assert_mysql_query_value(sql: &str, quoted: &str) {
522+
let stmt = mysql().one_statement_parses_to(sql, "");
523+
524+
match stmt {
525+
Statement::Query(query) => match *query.body {
526+
SetExpr::Select(value) => {
527+
let expr = expr_from_projection(only(&value.projection));
528+
assert_eq!(
529+
*expr,
530+
Expr::Value(Value::SingleQuotedString(quoted.to_string()))
531+
);
532+
}
533+
_ => unreachable!(),
534+
},
534535
_ => unreachable!(),
535-
},
536-
_ => unreachable!(),
537-
};
536+
};
537+
}
538+
let sql = r#"SELECT 'I\'m fine'"#;
539+
assert_mysql_query_value(sql, "I'm fine");
538540

539541
let sql = r#"SELECT 'I''m fine'"#;
542+
assert_mysql_query_value(sql, "I'm fine");
540543

541-
let projection = mysql().verified_only_select(sql).projection;
542-
let item = projection.get(0).unwrap();
544+
let sql = r#"SELECT 'I\"m fine'"#;
545+
assert_mysql_query_value(sql, "I\"m fine");
543546

544-
match &item {
545-
SelectItem::UnnamedExpr(Expr::Value(value)) => {
546-
assert_eq!(*value, Value::SingleQuotedString("I'm fine".to_string()));
547-
}
548-
_ => unreachable!(),
549-
}
547+
let sql = r#"SELECT 'Testing: \0 \\ \% \_ \b \n \r \t \Z \a \ '"#;
548+
assert_mysql_query_value(sql, "Testing: \0 \\ % _ \u{8} \n \r \t \u{1a} a ");
550549
}
551550

552551
#[test]

0 commit comments

Comments
 (0)