Skip to content

Commit

Permalink
Support SQLite CREATE VIRTUAL TABLE (sqlparser-rs#209)
Browse files Browse the repository at this point in the history
`CREATE VIRTUAL TABLE .. USING <module_name> (<module_args>)`

https://www.sqlite.org/lang_createvtab.html
  • Loading branch information
miuy56dc committed Jun 28, 2020
1 parent 0c83e5d commit a53f1d2
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -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

Expand Down
25 changes: 25 additions & 0 deletions src/ast/mod.rs
Expand Up @@ -484,6 +484,13 @@ pub enum Statement {
query: Option<Box<Query>>,
without_rowid: bool,
},
/// SQLite's `CREATE VIRTUAL TABLE .. USING <module_name> (<module_args>)`
CreateVirtualTable {
name: ObjectName,
if_not_exists: bool,
module_name: Ident,
module_args: Vec<Ident>,
},
/// CREATE INDEX
CreateIndex {
/// index name
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/dialect/keywords.rs
Expand Up @@ -438,6 +438,7 @@ define_keywords!(
VAR_SAMP,
VERSIONING,
VIEW,
VIRTUAL,
WHEN,
WHENEVER,
WHERE,
Expand Down
28 changes: 24 additions & 4 deletions src/parser.rs
Expand Up @@ -55,6 +55,7 @@ pub enum IsLateral {
Lateral,
NotLateral,
}
use crate::ast::Statement::CreateVirtualTable;
use IsLateral::*;

impl From<TokenizerError> for ParserError {
Expand Down Expand Up @@ -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<Statement, ParserError> {
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<Statement, ParserError> {
let schema_name = self.parse_object_name()?;
Ok(Statement::CreateSchema { schema_name })
Expand Down
22 changes: 22 additions & 0 deletions tests/sqlparser_sqlite.rs
Expand Up @@ -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
Expand Down

0 comments on commit a53f1d2

Please sign in to comment.