Added support for Mysql Backslash escapes (enabled by default) (#844)

This commit is contained in:
Coby Geralnik 2023-04-10 16:56:01 +03:00 committed by GitHub
parent 00d071286b
commit 04d9f3af2e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 41 additions and 31 deletions

View file

@ -1094,16 +1094,11 @@ impl<'a> Tokenizer<'a> {
chars.next(); // consume the opening quote chars.next(); // consume the opening quote
// slash escaping is specific to MySQL dialect
let mut is_escaped = false;
while let Some(&ch) = chars.peek() { while let Some(&ch) = chars.peek() {
match ch { match ch {
char if char == quote_style => { char if char == quote_style => {
chars.next(); // consume chars.next(); // consume
if is_escaped { if chars.peek().map(|c| *c == quote_style).unwrap_or(false) {
s.push(ch);
is_escaped = false;
} else if chars.peek().map(|c| *c == quote_style).unwrap_or(false) {
s.push(ch); s.push(ch);
chars.next(); chars.next();
} else { } else {
@ -1111,12 +1106,28 @@ impl<'a> Tokenizer<'a> {
} }
} }
'\\' => { '\\' => {
// consume
chars.next();
// slash escaping is specific to MySQL dialect
if dialect_of!(self is MySqlDialect) { if dialect_of!(self is MySqlDialect) {
is_escaped = !is_escaped; if let Some(next) = chars.peek() {
// See https://dev.mysql.com/doc/refman/8.0/en/string-literals.html#character-escape-sequences
let n = match next {
'\'' | '\"' | '\\' | '%' | '_' => *next,
'0' => '\0',
'b' => '\u{8}',
'n' => '\n',
'r' => '\r',
't' => '\t',
'Z' => '\u{1a}',
_ => *next,
};
s.push(n);
chars.next(); // consume next
}
} else { } else {
s.push(ch); s.push(ch);
} }
chars.next();
} }
_ => { _ => {
chars.next(); // consume chars.next(); // consume

View file

@ -518,35 +518,34 @@ fn parse_unterminated_escape() {
#[test] #[test]
fn parse_escaped_string() { fn parse_escaped_string() {
let sql = r#"SELECT 'I\'m fine'"#; fn assert_mysql_query_value(sql: &str, quoted: &str) {
let stmt = mysql().one_statement_parses_to(sql, "");
let stmt = mysql().one_statement_parses_to(sql, ""); match stmt {
Statement::Query(query) => match *query.body {
match stmt { SetExpr::Select(value) => {
Statement::Query(query) => match *query.body { let expr = expr_from_projection(only(&value.projection));
SetExpr::Select(value) => { assert_eq!(
let expr = expr_from_projection(only(&value.projection)); *expr,
assert_eq!( Expr::Value(Value::SingleQuotedString(quoted.to_string()))
*expr, );
Expr::Value(Value::SingleQuotedString("I'm fine".to_string())) }
); _ => unreachable!(),
} },
_ => unreachable!(), _ => unreachable!(),
}, };
_ => unreachable!(), }
}; let sql = r#"SELECT 'I\'m fine'"#;
assert_mysql_query_value(sql, "I'm fine");
let sql = r#"SELECT 'I''m fine'"#; let sql = r#"SELECT 'I''m fine'"#;
assert_mysql_query_value(sql, "I'm fine");
let projection = mysql().verified_only_select(sql).projection; let sql = r#"SELECT 'I\"m fine'"#;
let item = projection.get(0).unwrap(); assert_mysql_query_value(sql, "I\"m fine");
match &item { let sql = r#"SELECT 'Testing: \0 \\ \% \_ \b \n \r \t \Z \a \ '"#;
SelectItem::UnnamedExpr(Expr::Value(value)) => { assert_mysql_query_value(sql, "Testing: \0 \\ % _ \u{8} \n \r \t \u{1a} a ");
assert_eq!(*value, Value::SingleQuotedString("I'm fine".to_string()));
}
_ => unreachable!(),
}
} }
#[test] #[test]