Skip to content

Commit

Permalink
Fix escaping of trailing quote in quoted identifiers (#505)
Browse files Browse the repository at this point in the history
* Generalize EscapeSingleQuoteString to arbitrary quote character

* Fix escaping of trailing quote in quoted identifiers

* Add new tests instead of modifying existing tests
  • Loading branch information
Riccardo Azzolini committed May 27, 2022
1 parent cc2559c commit d19c6c3
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 18 deletions.
14 changes: 3 additions & 11 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use alloc::{
string::{String, ToString},
vec::Vec,
};
use core::fmt::{self, Write};
use core::fmt;

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -128,16 +128,8 @@ impl fmt::Display for Ident {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.quote_style {
Some(q) if q == '"' || q == '\'' || q == '`' => {
f.write_char(q)?;
let mut first = true;
for s in self.value.split_inclusive(q) {
if !first {
f.write_char(q)?;
}
first = false;
f.write_str(s)?;
}
f.write_char(q)
let escaped = value::escape_quoted_string(&self.value, q);
write!(f, "{}{}{}", q, escaped, q)
}
Some(q) if q == '[' => write!(f, "[{}]", self.value),
None => f.write_str(&self.value),
Expand Down
21 changes: 14 additions & 7 deletions src/ast/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,13 +178,16 @@ impl fmt::Display for DateTimeField {
}
}

pub struct EscapeSingleQuoteString<'a>(&'a str);
pub struct EscapeQuotedString<'a> {
string: &'a str,
quote: char,
}

impl<'a> fmt::Display for EscapeSingleQuoteString<'a> {
impl<'a> fmt::Display for EscapeQuotedString<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for c in self.0.chars() {
if c == '\'' {
write!(f, "\'\'")?;
for c in self.string.chars() {
if c == self.quote {
write!(f, "{q}{q}", q = self.quote)?;
} else {
write!(f, "{}", c)?;
}
Expand All @@ -193,8 +196,12 @@ impl<'a> fmt::Display for EscapeSingleQuoteString<'a> {
}
}

pub fn escape_single_quote_string(s: &str) -> EscapeSingleQuoteString<'_> {
EscapeSingleQuoteString(s)
pub fn escape_quoted_string(string: &str, quote: char) -> EscapeQuotedString<'_> {
EscapeQuotedString { string, quote }
}

pub fn escape_single_quote_string(s: &str) -> EscapeQuotedString<'_> {
escape_quoted_string(s, '\'')
}

pub struct EscapeEscapedStringLiteral<'a>(&'a str);
Expand Down
34 changes: 34 additions & 0 deletions tests/sqlparser_mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,40 @@ fn parse_quote_identifiers_2() {
);
}

#[test]
fn parse_quote_identifiers_3() {
let sql = "SELECT ```quoted identifier```";
assert_eq!(
mysql().verified_stmt(sql),
Statement::Query(Box::new(Query {
with: None,
body: SetExpr::Select(Box::new(Select {
distinct: false,
top: None,
projection: vec![SelectItem::UnnamedExpr(Expr::Identifier(Ident {
value: "`quoted identifier`".into(),
quote_style: Some('`'),
}))],
into: None,
from: vec![],
lateral_views: vec![],
selection: None,
group_by: vec![],
cluster_by: vec![],
distribute_by: vec![],
sort_by: vec![],
having: None,
qualify: None
})),
order_by: vec![],
limit: None,
offset: None,
fetch: None,
lock: None,
}))
);
}

#[test]
fn parse_unterminated_escape() {
let sql = r#"SELECT 'I\'m not fine\'"#;
Expand Down
5 changes: 5 additions & 0 deletions tests/sqlparser_postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1441,6 +1441,11 @@ fn parse_quoted_identifier() {
pg_and_generic().verified_stmt(r#"SELECT "quoted "" ident""#);
}

#[test]
fn parse_quoted_identifier_2() {
pg_and_generic().verified_stmt(r#"SELECT """quoted ident""""#);
}

#[test]
fn parse_local_and_global() {
pg_and_generic().verified_stmt("CREATE LOCAL TEMPORARY TABLE table (COL INT)");
Expand Down

0 comments on commit d19c6c3

Please sign in to comment.