diff --git a/src/lexer/sql/test.rs b/src/lexer/sql/test.rs index 54fb927..28d0228 100644 --- a/src/lexer/sql/test.rs +++ b/src/lexer/sql/test.rs @@ -48,6 +48,14 @@ fn duplicate_column() { b"CREATE TABLE t (x TEXT, x TEXT)", "duplicate column name: x", ); + expect_parser_err_msg( + b"CREATE TABLE t (x TEXT, \"x\" TEXT)", + "duplicate column name: x", + ); + expect_parser_err_msg( + b"CREATE TABLE t (x TEXT, `x` TEXT)", + "duplicate column name: x", + ); } #[test] diff --git a/src/parser/ast/fmt.rs b/src/parser/ast/fmt.rs index 7919313..a548b64 100644 --- a/src/parser/ast/fmt.rs +++ b/src/parser/ast/fmt.rs @@ -2035,3 +2035,57 @@ fn double_quote(name: &str, s: &mut S) -> Result<(), S::Error> { f.write_char('"')*/ s.append(TK_ID, Some(name)) } + +/// Convert an SQL-style quoted string into a normal string by removing +/// the quote characters. +pub fn dequote(n: Name) -> Result { + let s = n.0.as_str(); + if s.is_empty() { + return Ok(n); + } + let mut quote = s.chars().nth(0).unwrap(); + if quote != '"' && quote != '`' && quote != '\'' && quote != '[' { + return Ok(n); + } else if quote == '[' { + quote = ']'; + } + debug_assert!(s.len() > 1); + debug_assert!(s.ends_with(quote)); + let sub = &s[1..s.len() - 1]; + let mut z = String::with_capacity(sub.len()); + let mut escaped = false; + for c in sub.chars() { + if escaped { + if c != quote { + return Err(custom_err!("Malformed string literal: {}", s)); + } + escaped = false; + } else if c == quote { + escaped = true; + continue; + } + z.push(c); + } + Ok(Name(Uncased::from_owned(z))) +} + +#[cfg(test)] +mod test { + use super::{dequote, Name, ParserError}; + use uncased::Uncased; + + #[test] + fn test_dequote() -> Result<(), ParserError> { + assert_eq!(dequote(name("x"))?, name("x")); + assert_eq!(dequote(name("`x`"))?, name("x")); + assert_eq!(dequote(name("`x``y`"))?, name("x`y")); + assert_eq!(dequote(name(r#""x""#))?, name("x")); + assert_eq!(dequote(name(r#""x""y""#))?, name("x\"y")); + assert_eq!(dequote(name("[x]"))?, name("x")); + Ok(()) + } + + fn name(s: &'static str) -> Name { + Name(Uncased::from_borrowed(s)) + } +} diff --git a/src/parser/ast/mod.rs b/src/parser/ast/mod.rs index 8f295ed..b345254 100644 --- a/src/parser/ast/mod.rs +++ b/src/parser/ast/mod.rs @@ -6,7 +6,7 @@ pub mod fmt; use std::num::ParseIntError; use std::str::FromStr; -use fmt::{ToTokens, TokenStream}; +use fmt::{dequote, ToTokens, TokenStream}; use indexmap::{IndexMap, IndexSet}; use uncased::Uncased; @@ -1116,9 +1116,10 @@ impl ColumnDefinition { columns: &mut IndexMap, mut cd: ColumnDefinition, ) -> Result<(), ParserError> { - if columns.contains_key(&cd.col_name) { + let col_name = dequote(cd.col_name.clone())?; + if columns.contains_key(&col_name) { // TODO unquote - return Err(custom_err!("duplicate column name: {}", cd.col_name)); + return Err(custom_err!("duplicate column name: {}", col_name)); } // https://github.com/sqlite/sqlite/blob/e452bf40a14aca57fd9047b330dff282f3e4bbcc/src/build.c#L1511-L1514 if let Some(ref mut col_type) = cd.col_type { @@ -1147,7 +1148,7 @@ impl ColumnDefinition { col_type.name = new_type.join(" "); } } - columns.insert(cd.col_name.clone(), cd); + columns.insert(col_name, cd); Ok(()) } } diff --git a/src/parser/parse.y b/src/parser/parse.y index bbace83..4e8614e 100644 --- a/src/parser/parse.y +++ b/src/parser/parse.y @@ -166,8 +166,9 @@ columnlist(A) ::= columnlist(A) COMMA columnname(X) carglist(Y). { } columnlist(A) ::= columnname(X) carglist(Y). { let col = X; + let cd = ColumnDefinition{ col_name: col.0, col_type: col.1, constraints: Y }; let mut map = IndexMap::new(); - map.insert(col.0.clone(), ColumnDefinition{ col_name: col.0, col_type: col.1, constraints: Y }); + ColumnDefinition::add_column(&mut map, cd)?; A = map; } %type columnname {(Name, Option)}