diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 3298a1de..95ecf792 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -8,8 +8,16 @@ pub use self::generic_sql::GenericSqlDialect; pub use self::postgresql::PostgreSqlDialect; pub trait Dialect { - /// Determine if a character is a valid identifier start character + /// Determine if a character starts a quoted identifier. The default + /// implementation, accepting "double quoted" ids is both ANSI-compliant + /// and appropriate for most dialects (with the notable exception of + /// MySQL, MS SQL, and sqlite). You can accept one of characters listed + /// in `SQLWord::matching_end_quote()` here + fn is_delimited_identifier_start(&self, ch: char) -> bool { + ch == '"' + } + /// Determine if a character is a valid start character for an unquoted identifier fn is_identifier_start(&self, ch: char) -> bool; - /// Determine if a character is a valid identifier character + /// Determine if a character is a valid unquoted identifier character fn is_identifier_part(&self, ch: char) -> bool; } diff --git a/src/sqltokenizer.rs b/src/sqltokenizer.rs index f8e7ef8f..602db561 100644 --- a/src/sqltokenizer.rs +++ b/src/sqltokenizer.rs @@ -163,13 +163,24 @@ pub struct SQLWord { impl ToString for SQLWord { fn to_string(&self) -> String { match self.quote_style { - Some('"') => format!("\"{}\"", self.value), - Some('[') => format!("[{}]", self.value), + Some(s) if s == '"' || s == '[' || s == '`' => { + format!("{}{}{}", s, self.value, SQLWord::matching_end_quote(s)) + } None => self.value.clone(), _ => panic!("Unexpected quote_style!"), } } } +impl SQLWord { + fn matching_end_quote(ch: char) -> char { + match ch { + '"' => '"', // ANSI and most dialects + '[' => ']', // MS SQL + '`' => '`', // MySQL + _ => panic!("unexpected quoting style!"), + } + } +} #[derive(Debug, Clone, PartialEq)] pub enum Whitespace { @@ -291,12 +302,13 @@ impl<'a> Tokenizer<'a> { Ok(Some(Token::SingleQuotedString(s))) } // delimited (quoted) identifier - '"' => { + quote_start if self.dialect.is_delimited_identifier_start(quote_start) => { let mut s = String::new(); - let quote_start = chars.next().unwrap(); // consumes the opening quote + chars.next(); // consume the opening quote + let quote_end = SQLWord::matching_end_quote(quote_start); while let Some(ch) = chars.next() { match ch { - '"' => break, + c if c == quote_end => break, _ => s.push(ch), } }