diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index c9ddbedd31..ff28314c89 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -16,6 +16,7 @@ pub mod keywords; mod mssql; mod mysql; mod postgresql; +mod sqlite; use std::fmt::Debug; @@ -24,6 +25,7 @@ pub use self::generic::GenericDialect; pub use self::mssql::MsSqlDialect; pub use self::mysql::MySqlDialect; pub use self::postgresql::PostgreSqlDialect; +pub use self::sqlite::SQLiteDialect; pub trait Dialect: Debug { /// Determine if a character starts a quoted identifier. The default diff --git a/src/dialect/sqlite.rs b/src/dialect/sqlite.rs new file mode 100644 index 0000000000..16ec66ac25 --- /dev/null +++ b/src/dialect/sqlite.rs @@ -0,0 +1,38 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::dialect::Dialect; + +#[derive(Debug)] +pub struct SQLiteDialect {} + +impl Dialect for SQLiteDialect { + // see https://www.sqlite.org/lang_keywords.html + // parse `...`, [...] and "..." as identifier + // TODO: support depending on the context tread '...' as identifier too. + fn is_delimited_identifier_start(&self, ch: char) -> bool { + ch == '`' || ch == '"' || ch == '[' + } + + fn is_identifier_start(&self, ch: char) -> bool { + // See https://www.sqlite.org/draft/tokenreq.html + (ch >= 'a' && ch <= 'z') + || (ch >= 'A' && ch <= 'Z') + || ch == '_' + || ch == '$' + || (ch >= '\u{007f}' && ch <= '\u{ffff}') + } + + fn is_identifier_part(&self, ch: char) -> bool { + self.is_identifier_start(ch) || (ch >= '0' && ch <= '9') + } +} diff --git a/tests/sqlparser_sqlite.rs b/tests/sqlparser_sqlite.rs index ca6a677be8..2a421e94b2 100644 --- a/tests/sqlparser_sqlite.rs +++ b/tests/sqlparser_sqlite.rs @@ -15,7 +15,7 @@ //! generic dialect is also tested (on the inputs it can handle). use sqlparser::ast::*; -use sqlparser::dialect::GenericDialect; +use sqlparser::dialect::{GenericDialect, SQLiteDialect}; use sqlparser::test_utils::*; use sqlparser::tokenizer::Token; @@ -87,9 +87,43 @@ fn parse_create_table_auto_increment() { } } +#[test] +fn parse_create_sqlite_quote() { + let sql = "CREATE TABLE `PRIMARY` (\"KEY\" INT, [INDEX] INT)"; + match sqlite().verified_stmt(sql) { + Statement::CreateTable { name, columns, .. } => { + assert_eq!(name.to_string(), "`PRIMARY`"); + assert_eq!( + vec![ + ColumnDef { + name: Ident::with_quote('"', "KEY"), + data_type: DataType::Int, + collation: None, + options: vec![], + }, + ColumnDef { + name: Ident::with_quote('[', "INDEX"), + data_type: DataType::Int, + collation: None, + options: vec![], + }, + ], + columns + ); + } + _ => unreachable!(), + } +} + +fn sqlite() -> TestedDialects { + TestedDialects { + dialects: vec![Box::new(SQLiteDialect {})], + } +} + fn sqlite_and_generic() -> TestedDialects { TestedDialects { // we don't have a separate SQLite dialect, so test only the generic dialect for now - dialects: vec![Box::new(GenericDialect {})], + dialects: vec![Box::new(SQLiteDialect {}), Box::new(GenericDialect {})], } }