From 80dccf68856ef9ad019b25e86d0e2872b516c40c Mon Sep 17 00:00:00 2001 From: Justin Haug Date: Mon, 22 Apr 2019 13:32:05 -0400 Subject: [PATCH] Add support for escaping single quote strings --- src/sqlast/value.rs | 14 +++++++++++++- src/sqltokenizer.rs | 10 +++++++++- tests/sqlparser_generic.rs | 17 +++++++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/sqlast/value.rs b/src/sqlast/value.rs index a061080a..eedb14a9 100644 --- a/src/sqlast/value.rs +++ b/src/sqlast/value.rs @@ -35,7 +35,7 @@ impl ToString for Value { Value::Long(v) => v.to_string(), Value::Double(v) => v.to_string(), Value::Uuid(v) => v.to_string(), - Value::SingleQuotedString(v) => format!("'{}'", v), + Value::SingleQuotedString(v) => format!("'{}'", escape_single_quote_string(v)), Value::NationalStringLiteral(v) => format!("N'{}'", v), Value::Boolean(v) => v.to_string(), Value::Date(v) => v.to_string(), @@ -46,3 +46,15 @@ impl ToString for Value { } } } + +fn escape_single_quote_string(s: &str) -> String { + let mut escaped = String::new(); + for c in s.chars() { + if c == '\'' { + escaped.push_str("\'\'"); + } else { + escaped.push(c); + } + } + escaped +} diff --git a/src/sqltokenizer.rs b/src/sqltokenizer.rs index 83105736..8735627d 100644 --- a/src/sqltokenizer.rs +++ b/src/sqltokenizer.rs @@ -462,7 +462,15 @@ impl<'a> Tokenizer<'a> { match ch { '\'' => { chars.next(); // consume - break; + let escaped_quote = chars.peek() + .map(|c| *c == '\'') + .unwrap_or(false); + if escaped_quote { + s.push('\''); + chars.next(); + } else { + break; + } } _ => { chars.next(); // consume diff --git a/tests/sqlparser_generic.rs b/tests/sqlparser_generic.rs index ea60e6d4..647a9338 100644 --- a/tests/sqlparser_generic.rs +++ b/tests/sqlparser_generic.rs @@ -148,6 +148,23 @@ fn parse_projection_nested_type() { //TODO: add assertions } +#[test] +fn parse_escaped_single_quote_string_predicate() { + use self::ASTNode::*; + use self::SQLOperator::*; + let sql = "SELECT id, fname, lname FROM customer \ + WHERE salary != 'Jim''s salary'"; + let ast = verified_only_select(sql); + assert_eq!( + Some(SQLBinaryExpr { + left: Box::new(SQLIdentifier("salary".to_string())), + op: NotEq, + right: Box::new(SQLValue(Value::SingleQuotedString("Jim's salary".to_string()))) + }), + ast.selection, + ); +} + #[test] fn parse_compound_expr_1() { use self::ASTNode::*;