From d8c264aa5ec125a01331164c1620cd6ab0a599cb Mon Sep 17 00:00:00 2001 From: mashuai Date: Fri, 26 Jun 2020 21:08:52 +0800 Subject: [PATCH 1/3] Support SQLite `CREATE VIRTUAL TABLE` https://www.sqlite.org/lang_createvtab.html --- src/ast/mod.rs | 26 ++++++++++++++++++++++++++ src/dialect/keywords.rs | 1 + src/parser.rs | 20 +++++++++++++++++++- tests/sqlparser_sqlite.rs | 25 +++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 1 deletion(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 885935b3a7..2d30ee66bb 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -484,6 +484,14 @@ pub enum Statement { query: Option>, without_rowid: bool, }, + // CREATE VIRTUAL TABLE + CreateVirtualTable { + /// Virtual Table name + name: ObjectName, + if_not_exists: bool, + module_name: Ident, + module_args: Vec, + }, /// CREATE INDEX CreateIndex { /// index name @@ -695,6 +703,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..0a9eff64ea 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,33 @@ 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", + "TABLE, VIEW, INDEX, SCHEMA or VIRTUAL after CREATE", self.peek_token(), ) } } + 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()?; + 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..b798394e24 100644 --- a/tests/sqlparser_sqlite.rs +++ b/tests/sqlparser_sqlite.rs @@ -33,6 +33,31 @@ 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().one_statement_parses_to( + sql, + "CREATE VIRTUAL TABLE IF NOT EXISTS t USING module_name (arg1, arg2)", + ) { + 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().one_statement_parses_to(sql, "CREATE VIRTUAL TABLE t USING module_name"); +} + fn sqlite_and_generic() -> TestedDialects { TestedDialects { // we don't have a separate SQLite dialect, so test only the generic dialect for now From 5de25afc439ac9db6b89a0b43868179b5c914573 Mon Sep 17 00:00:00 2001 From: Nickolay Ponomarev Date: Sun, 28 Jun 2020 04:00:09 +0300 Subject: [PATCH 2/3] Minor follow-ups --- CHANGELOG.md | 1 + src/ast/mod.rs | 3 +-- src/parser.rs | 10 ++++++---- 3 files changed, 8 insertions(+), 6 deletions(-) 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 2d30ee66bb..9486f16c6b 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -484,9 +484,8 @@ pub enum Statement { query: Option>, without_rowid: bool, }, - // CREATE VIRTUAL TABLE + /// SQLite's `CREATE VIRTUAL TABLE .. USING ()` CreateVirtualTable { - /// Virtual Table name name: ObjectName, if_not_exists: bool, module_name: Ident, diff --git a/src/parser.rs b/src/parser.rs index 0a9eff64ea..a1e542484f 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -992,19 +992,21 @@ impl Parser { } else if self.parse_keyword(Keyword::SCHEMA) { self.parse_create_schema() } else { - self.expected( - "TABLE, VIEW, INDEX, SCHEMA or VIRTUAL 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, From 60f60d9486b4469d29b0f09c4d2ac8c42c42a6b8 Mon Sep 17 00:00:00 2001 From: Nickolay Ponomarev Date: Sun, 28 Jun 2020 04:27:20 +0300 Subject: [PATCH 3/3] Use `verified_stmt` instead of verified_stmt() to test the serializer --- tests/sqlparser_sqlite.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/sqlparser_sqlite.rs b/tests/sqlparser_sqlite.rs index b798394e24..6d715abea9 100644 --- a/tests/sqlparser_sqlite.rs +++ b/tests/sqlparser_sqlite.rs @@ -36,10 +36,7 @@ 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().one_statement_parses_to( - 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, @@ -55,7 +52,7 @@ fn parse_create_virtual_table() { } let sql = "CREATE VIRTUAL TABLE t USING module_name"; - sqlite_and_generic().one_statement_parses_to(sql, "CREATE VIRTUAL TABLE t USING module_name"); + sqlite_and_generic().verified_stmt(sql); } fn sqlite_and_generic() -> TestedDialects {