mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-09 08:48:02 +00:00
Fix escaping of trailing quote in quoted identifiers (#505)
* Generalize EscapeSingleQuoteString to arbitrary quote character * Fix escaping of trailing quote in quoted identifiers * Add new tests instead of modifying existing tests
This commit is contained in:
parent
cc2559c097
commit
d19c6c323c
4 changed files with 56 additions and 18 deletions
|
@ -23,7 +23,7 @@ use alloc::{
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
vec::Vec,
|
vec::Vec,
|
||||||
};
|
};
|
||||||
use core::fmt::{self, Write};
|
use core::fmt;
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -128,16 +128,8 @@ impl fmt::Display for Ident {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self.quote_style {
|
match self.quote_style {
|
||||||
Some(q) if q == '"' || q == '\'' || q == '`' => {
|
Some(q) if q == '"' || q == '\'' || q == '`' => {
|
||||||
f.write_char(q)?;
|
let escaped = value::escape_quoted_string(&self.value, q);
|
||||||
let mut first = true;
|
write!(f, "{}{}{}", q, escaped, q)
|
||||||
for s in self.value.split_inclusive(q) {
|
|
||||||
if !first {
|
|
||||||
f.write_char(q)?;
|
|
||||||
}
|
|
||||||
first = false;
|
|
||||||
f.write_str(s)?;
|
|
||||||
}
|
|
||||||
f.write_char(q)
|
|
||||||
}
|
}
|
||||||
Some(q) if q == '[' => write!(f, "[{}]", self.value),
|
Some(q) if q == '[' => write!(f, "[{}]", self.value),
|
||||||
None => f.write_str(&self.value),
|
None => f.write_str(&self.value),
|
||||||
|
|
|
@ -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 {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
for c in self.0.chars() {
|
for c in self.string.chars() {
|
||||||
if c == '\'' {
|
if c == self.quote {
|
||||||
write!(f, "\'\'")?;
|
write!(f, "{q}{q}", q = self.quote)?;
|
||||||
} else {
|
} else {
|
||||||
write!(f, "{}", c)?;
|
write!(f, "{}", c)?;
|
||||||
}
|
}
|
||||||
|
@ -193,8 +196,12 @@ impl<'a> fmt::Display for EscapeSingleQuoteString<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn escape_single_quote_string(s: &str) -> EscapeSingleQuoteString<'_> {
|
pub fn escape_quoted_string(string: &str, quote: char) -> EscapeQuotedString<'_> {
|
||||||
EscapeSingleQuoteString(s)
|
EscapeQuotedString { string, quote }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn escape_single_quote_string(s: &str) -> EscapeQuotedString<'_> {
|
||||||
|
escape_quoted_string(s, '\'')
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EscapeEscapedStringLiteral<'a>(&'a str);
|
pub struct EscapeEscapedStringLiteral<'a>(&'a str);
|
||||||
|
|
|
@ -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]
|
#[test]
|
||||||
fn parse_unterminated_escape() {
|
fn parse_unterminated_escape() {
|
||||||
let sql = r#"SELECT 'I\'m not fine\'"#;
|
let sql = r#"SELECT 'I\'m not fine\'"#;
|
||||||
|
|
|
@ -1441,6 +1441,11 @@ fn parse_quoted_identifier() {
|
||||||
pg_and_generic().verified_stmt(r#"SELECT "quoted "" ident""#);
|
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]
|
#[test]
|
||||||
fn parse_local_and_global() {
|
fn parse_local_and_global() {
|
||||||
pg_and_generic().verified_stmt("CREATE LOCAL TEMPORARY TABLE table (COL INT)");
|
pg_and_generic().verified_stmt("CREATE LOCAL TEMPORARY TABLE table (COL INT)");
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue