diff --git a/CHANGELOG.md b/CHANGELOG.md index 888ce51a5e..1d07b33fa2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Check https://github.com/andygrove/sqlparser-rs/commits/master for undocumented ### Added - Support SQLite's `CREATE TABLE (...) WITHOUT ROWID` (#208) - thanks @mashuai! +- Support SQLite's `CREATE VIRTUAL TABLE` (#209) - thanks @mashuai! ### Fixed diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 885935b3a7..9486f16c6b 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -484,6 +484,13 @@ pub enum Statement { query: Option>, without_rowid: bool, }, + /// SQLite's `CREATE VIRTUAL TABLE .. USING ()` + CreateVirtualTable { + name: ObjectName, + if_not_exists: bool, + module_name: Ident, + module_args: Vec, + }, /// CREATE INDEX CreateIndex { /// index name @@ -695,6 +702,24 @@ impl fmt::Display for Statement { } Ok(()) } + Statement::CreateVirtualTable { + name, + if_not_exists, + module_name, + module_args, + } => { + write!( + f, + "CREATE VIRTUAL TABLE {if_not_exists}{name} USING {module_name}", + if_not_exists = if *if_not_exists { "IF NOT EXISTS " } else { "" }, + name = name, + module_name = module_name + )?; + if !module_args.is_empty() { + write!(f, " ({})", display_comma_separated(module_args))?; + } + Ok(()) + } Statement::CreateIndex { name, table_name, diff --git a/src/dialect/keywords.rs b/src/dialect/keywords.rs index 8f8047aa53..b6cfe35e99 100644 --- a/src/dialect/keywords.rs +++ b/src/dialect/keywords.rs @@ -438,6 +438,7 @@ define_keywords!( VAR_SAMP, VERSIONING, VIEW, + VIRTUAL, WHEN, WHENEVER, WHERE, diff --git a/src/parser.rs b/src/parser.rs index d4c894db7d..a1e542484f 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -55,6 +55,7 @@ pub enum IsLateral { Lateral, NotLateral, } +use crate::ast::Statement::CreateVirtualTable; use IsLateral::*; impl From for ParserError { @@ -986,16 +987,35 @@ impl Parser { self.parse_create_view() } else if self.parse_keyword(Keyword::EXTERNAL) { self.parse_create_external_table() + } else if self.parse_keyword(Keyword::VIRTUAL) { + self.parse_create_virtual_table() } else if self.parse_keyword(Keyword::SCHEMA) { self.parse_create_schema() } else { - self.expected( - "TABLE, VIEW, INDEX or SCHEMA after CREATE", - self.peek_token(), - ) + self.expected("an object type after CREATE", self.peek_token()) } } + /// SQLite-specific `CREATE VIRTUAL TABLE` + pub fn parse_create_virtual_table(&mut self) -> Result { + self.expect_keyword(Keyword::TABLE)?; + let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]); + let table_name = self.parse_object_name()?; + self.expect_keyword(Keyword::USING)?; + let module_name = self.parse_identifier()?; + // SQLite docs note that module "arguments syntax is sufficiently + // general that the arguments can be made to appear as column + // definitions in a traditional CREATE TABLE statement", but + // we don't implement that. + let module_args = self.parse_parenthesized_column_list(Optional)?; + Ok(CreateVirtualTable { + name: table_name, + if_not_exists, + module_name, + module_args, + }) + } + pub fn parse_create_schema(&mut self) -> Result { let schema_name = self.parse_object_name()?; Ok(Statement::CreateSchema { schema_name }) diff --git a/tests/sqlparser_sqlite.rs b/tests/sqlparser_sqlite.rs index 7a1ce816ad..6d715abea9 100644 --- a/tests/sqlparser_sqlite.rs +++ b/tests/sqlparser_sqlite.rs @@ -33,6 +33,28 @@ fn parse_create_table_without_rowid() { } } +#[test] +fn parse_create_virtual_table() { + let sql = "CREATE VIRTUAL TABLE IF NOT EXISTS t USING module_name (arg1, arg2)"; + match sqlite_and_generic().verified_stmt(sql) { + Statement::CreateVirtualTable { + name, + if_not_exists: true, + module_name, + module_args, + } => { + let args = vec![Ident::new("arg1"), Ident::new("arg2")]; + assert_eq!("t", name.to_string()); + assert_eq!("module_name", module_name.to_string()); + assert_eq!(args, module_args); + } + _ => unreachable!(), + } + + let sql = "CREATE VIRTUAL TABLE t USING module_name"; + sqlite_and_generic().verified_stmt(sql); +} + fn sqlite_and_generic() -> TestedDialects { TestedDialects { // we don't have a separate SQLite dialect, so test only the generic dialect for now