Skip to content

Commit 8b04b40

Browse files
committed
fixes
1 parent a672844 commit 8b04b40

File tree

5 files changed

+43
-32
lines changed

5 files changed

+43
-32
lines changed

src/ast/mod.rs

-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
// limitations under the License.
1212

1313
//! SQL Abstract Syntax Tree (AST) types
14-
1514
mod data_type;
1615
mod ddl;
1716
mod operator;

src/ast/value.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -143,14 +143,12 @@ pub struct EscapeSingleQuoteString<'a>(&'a str);
143143

144144
impl<'a> fmt::Display for EscapeSingleQuoteString<'a> {
145145
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
146-
let mut cur_escaped = false;
147146
for c in self.0.chars() {
148147
if c == '\'' {
149-
write!(f, "{}", if cur_escaped { "\'" } else { "\'\'" })?;
148+
write!(f, "\'\'")?;
150149
} else {
151150
write!(f, "{}", c)?;
152151
}
153-
cur_escaped = c == '\\';
154152
}
155153
Ok(())
156154
}

src/dialect/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ pub use self::sqlite::SQLiteDialect;
3535
pub use crate::keywords;
3636

3737
/// `dialect_of!(parser is SQLiteDialect | GenericDialect)` evaluates
38-
/// to `true` iff `parser.dialect` is one of the `Dialect`s specified.
38+
/// to `true` if `parser.dialect` is one of the `Dialect`s specified.
3939
macro_rules! dialect_of {
4040
( $parsed_dialect: ident is $($dialect_type: ty)|+ ) => {
4141
($($parsed_dialect.dialect.is::<$dialect_type>())||+)

src/tokenizer.rs

+14-19
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ use core::str::Chars;
3131
#[cfg(feature = "serde")]
3232
use serde::{Deserialize, Serialize};
3333

34-
use crate::dialect::Dialect;
3534
use crate::dialect::SnowflakeDialect;
35+
use crate::dialect::{Dialect, MySqlDialect};
3636
use crate::keywords::{Keyword, ALL_KEYWORDS, ALL_KEYWORDS_INDEX};
3737

3838
/// SQL Token enumeration
@@ -637,35 +637,30 @@ impl<'a> Tokenizer<'a> {
637637
) -> Result<String, TokenizerError> {
638638
let mut s = String::new();
639639
chars.next(); // consume the opening quote
640+
641+
// slash escaping is specific to MySQL dialect
642+
let mut is_escaped = false;
640643
while let Some(&ch) = chars.peek() {
641644
match ch {
642645
'\'' => {
643646
chars.next(); // consume
644-
let escaped_quote = chars.peek().map(|c| *c == '\'').unwrap_or(false);
645-
if escaped_quote {
646-
s.push('\'');
647+
if is_escaped {
648+
s.push(ch);
649+
is_escaped = false;
650+
} else if chars.peek().map(|c| *c == '\'').unwrap_or(false) {
651+
s.push(ch);
647652
chars.next();
648653
} else {
649654
return Ok(s);
650655
}
651656
}
652657
'\\' => {
653-
chars.next();
654-
match chars.peek() {
655-
None => {
656-
return self.tokenizer_error("Unterminated escape sequence");
657-
}
658-
Some(&c) => {
659-
chars.next();
660-
match c {
661-
'\'' => {
662-
s.push('\\');
663-
s.push('\'');
664-
}
665-
x => s.push(x),
666-
}
667-
}
658+
if dialect_of!(self is MySqlDialect) {
659+
is_escaped = !is_escaped;
660+
} else {
661+
s.push(ch);
668662
}
663+
chars.next();
669664
}
670665
_ => {
671666
chars.next(); // consume

tests/sqlparser_mysql.rs

+27-8
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ mod test_utils;
1919

2020
use test_utils::*;
2121

22+
use sqlparser::ast::Expr;
23+
use sqlparser::ast::Value;
2224
use sqlparser::ast::*;
2325
use sqlparser::dialect::{GenericDialect, MySqlDialect};
2426
use sqlparser::tokenizer::Token;
@@ -176,19 +178,36 @@ fn parse_quote_identifiers() {
176178
}
177179
}
178180

181+
#[test]
182+
fn parse_unterminated_escape() {
183+
let sql = r#"SELECT 'I\'m not fine\'"#;
184+
let result = std::panic::catch_unwind(|| mysql().one_statement_parses_to(sql, ""));
185+
assert!(result.is_err());
186+
187+
let sql = r#"SELECT 'I\\'m not fine'"#;
188+
let result = std::panic::catch_unwind(|| mysql().one_statement_parses_to(sql, ""));
189+
assert!(result.is_err());
190+
}
191+
179192
#[test]
180193
fn parse_escaped_string() {
181194
let sql = r#"SELECT 'I\'m fine'"#;
182195

183-
let projection = mysql().verified_only_select(sql).projection;
184-
let item = projection.get(0).unwrap();
196+
let stmt = mysql().one_statement_parses_to(sql, "");
185197

186-
match &item {
187-
SelectItem::UnnamedExpr(Expr::Value(value)) => {
188-
assert_eq!("'I\\'m fine'", value.to_string());
189-
}
198+
match stmt {
199+
Statement::Query(query) => match query.body {
200+
SetExpr::Select(value) => {
201+
let expr = expr_from_projection(only(&value.projection));
202+
assert_eq!(
203+
*expr,
204+
Expr::Value(Value::SingleQuotedString("I'm fine".to_string()))
205+
);
206+
}
207+
_ => unreachable!(),
208+
},
190209
_ => unreachable!(),
191-
}
210+
};
192211

193212
let sql = r#"SELECT 'I''m fine'"#;
194213

@@ -197,7 +216,7 @@ fn parse_escaped_string() {
197216

198217
match &item {
199218
SelectItem::UnnamedExpr(Expr::Value(value)) => {
200-
assert_eq!("'I''m fine'", value.to_string());
219+
assert_eq!(*value, Value::SingleQuotedString("I'm fine".to_string()));
201220
}
202221
_ => unreachable!(),
203222
}

0 commit comments

Comments
 (0)