From 5b464e6b1a9ca034739553997126bab2a07d8c77 Mon Sep 17 00:00:00 2001 From: Justin Haug Date: Mon, 22 Apr 2019 13:10:29 -0400 Subject: [PATCH 1/5] Fix qualified wildcard stringifying --- src/sqlast/mod.rs | 2 +- tests/sqlparser_ansi.rs | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/sqlast/mod.rs b/src/sqlast/mod.rs index eef10fcd..d5a2d787 100644 --- a/src/sqlast/mod.rs +++ b/src/sqlast/mod.rs @@ -111,7 +111,7 @@ impl ToString for ASTNode { match self { ASTNode::SQLIdentifier(s) => s.to_string(), ASTNode::SQLWildcard => "*".to_string(), - ASTNode::SQLQualifiedWildcard(q) => q.join(".") + "*", + ASTNode::SQLQualifiedWildcard(q) => q.join(".") + ".*", ASTNode::SQLCompoundIdentifier(s) => s.join("."), ASTNode::SQLIsNull(ast) => format!("{} IS NULL", ast.as_ref().to_string()), ASTNode::SQLIsNotNull(ast) => format!("{} IS NOT NULL", ast.as_ref().to_string()), diff --git a/tests/sqlparser_ansi.rs b/tests/sqlparser_ansi.rs index 73054fb7..6ffbd9b4 100644 --- a/tests/sqlparser_ansi.rs +++ b/tests/sqlparser_ansi.rs @@ -20,3 +20,16 @@ fn parse_simple_select() { _ => assert!(false), } } + +#[test] +fn roundtrip_qualified_wildcard() { + let sql = String::from("SELECT COUNT(Employee.*) FROM northwind.Order JOIN northwind.Employee ON Order.employee = Employee.id"); + let ast = Parser::parse_sql(&AnsiSqlDialect {}, sql.clone()).unwrap(); + assert_eq!(1, ast.len()); + match ast.first().unwrap() { + SQLStatement::SQLSelect(ref query) => { + assert_eq!(sql, query.to_string()); + } + _ => assert!(false), + } +} From 80dccf68856ef9ad019b25e86d0e2872b516c40c Mon Sep 17 00:00:00 2001 From: Justin Haug Date: Mon, 22 Apr 2019 13:32:05 -0400 Subject: [PATCH 2/5] 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::*; From 80aceba630da6015eb6f8be908298cdd416f41e6 Mon Sep 17 00:00:00 2001 From: Justin Haug Date: Mon, 22 Apr 2019 17:50:12 -0400 Subject: [PATCH 3/5] run cargo fmt --- src/sqltokenizer.rs | 4 +--- tests/sqlparser_generic.rs | 4 +++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sqltokenizer.rs b/src/sqltokenizer.rs index 8735627d..64faecab 100644 --- a/src/sqltokenizer.rs +++ b/src/sqltokenizer.rs @@ -462,9 +462,7 @@ impl<'a> Tokenizer<'a> { match ch { '\'' => { chars.next(); // consume - let escaped_quote = chars.peek() - .map(|c| *c == '\'') - .unwrap_or(false); + let escaped_quote = chars.peek().map(|c| *c == '\'').unwrap_or(false); if escaped_quote { s.push('\''); chars.next(); diff --git a/tests/sqlparser_generic.rs b/tests/sqlparser_generic.rs index 647a9338..e0466e5e 100644 --- a/tests/sqlparser_generic.rs +++ b/tests/sqlparser_generic.rs @@ -159,7 +159,9 @@ fn parse_escaped_single_quote_string_predicate() { Some(SQLBinaryExpr { left: Box::new(SQLIdentifier("salary".to_string())), op: NotEq, - right: Box::new(SQLValue(Value::SingleQuotedString("Jim's salary".to_string()))) + right: Box::new(SQLValue(Value::SingleQuotedString( + "Jim's salary".to_string() + ))) }), ast.selection, ); From f9fb4bedfb64b038c222eb0b9983eda98f57c5a4 Mon Sep 17 00:00:00 2001 From: Justin Haug Date: Mon, 22 Apr 2019 17:52:15 -0400 Subject: [PATCH 4/5] Move wildcard test to generic parser --- tests/sqlparser_ansi.rs | 13 ------------- tests/sqlparser_generic.rs | 5 +++++ 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/tests/sqlparser_ansi.rs b/tests/sqlparser_ansi.rs index 6ffbd9b4..73054fb7 100644 --- a/tests/sqlparser_ansi.rs +++ b/tests/sqlparser_ansi.rs @@ -20,16 +20,3 @@ fn parse_simple_select() { _ => assert!(false), } } - -#[test] -fn roundtrip_qualified_wildcard() { - let sql = String::from("SELECT COUNT(Employee.*) FROM northwind.Order JOIN northwind.Employee ON Order.employee = Employee.id"); - let ast = Parser::parse_sql(&AnsiSqlDialect {}, sql.clone()).unwrap(); - assert_eq!(1, ast.len()); - match ast.first().unwrap() { - SQLStatement::SQLSelect(ref query) => { - assert_eq!(sql, query.to_string()); - } - _ => assert!(false), - } -} diff --git a/tests/sqlparser_generic.rs b/tests/sqlparser_generic.rs index e0466e5e..d7b14bf2 100644 --- a/tests/sqlparser_generic.rs +++ b/tests/sqlparser_generic.rs @@ -91,6 +91,11 @@ fn parse_select_wildcard() { ); } +#[test] +fn parse_count_wildcard() { + verified_only_select("SELECT COUNT(Employee.*) FROM Order JOIN Employee ON Order.employee = Employee.id"); +} + #[test] fn parse_column_aliases() { let sql = "SELECT a.col + 1 AS newname FROM foo AS a"; From 76d2d46496edf1842556c12f126dccbac252b486 Mon Sep 17 00:00:00 2001 From: Justin Haug Date: Mon, 22 Apr 2019 18:06:00 -0400 Subject: [PATCH 5/5] run cargo fmt --- tests/sqlparser_generic.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/sqlparser_generic.rs b/tests/sqlparser_generic.rs index d7b14bf2..8481797d 100644 --- a/tests/sqlparser_generic.rs +++ b/tests/sqlparser_generic.rs @@ -93,7 +93,9 @@ fn parse_select_wildcard() { #[test] fn parse_count_wildcard() { - verified_only_select("SELECT COUNT(Employee.*) FROM Order JOIN Employee ON Order.employee = Employee.id"); + verified_only_select( + "SELECT COUNT(Employee.*) FROM Order JOIN Employee ON Order.employee = Employee.id", + ); } #[test]