diff --git a/src/ast/mod.rs b/src/ast/mod.rs index eb8830bb1..48274f68c 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -1429,6 +1429,16 @@ pub enum Statement { name: Ident, operation: AlterRoleOperation, }, + /// ATTACH DATABASE 'path/to/file' AS alias + /// (SQLite-specific) + AttachDatabase { + /// The name to bind to the newly attached database + schema_name: Ident, + /// An expression that indicates the path to the database file + database_file_name: Expr, + /// true if the syntax is 'ATTACH DATABASE', false if it's just 'ATTACH' + database: bool, + }, /// DROP Drop { /// The type of the object to drop: TABLE, VIEW, etc. @@ -1969,6 +1979,14 @@ impl fmt::Display for Statement { } Ok(()) } + Statement::AttachDatabase { + schema_name, + database_file_name, + database, + } => { + let keyword = if *database { "DATABASE " } else { "" }; + write!(f, "ATTACH {keyword}{database_file_name} AS {schema_name}") + } Statement::Analyze { table_name, partitions, diff --git a/src/keywords.rs b/src/keywords.rs index ad0526ccd..eee961350 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -95,6 +95,7 @@ define_keywords!( ASYMMETRIC, AT, ATOMIC, + ATTACH, AUTHORIZATION, AUTOINCREMENT, AUTO_INCREMENT, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index ba8f5784f..49cd24899 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -456,6 +456,7 @@ impl<'a> Parser<'a> { Ok(Statement::Query(Box::new(self.parse_query()?))) } Keyword::TRUNCATE => Ok(self.parse_truncate()?), + Keyword::ATTACH => Ok(self.parse_attach_database()?), Keyword::MSCK => Ok(self.parse_msck()?), Keyword::CREATE => Ok(self.parse_create()?), Keyword::CACHE => Ok(self.parse_cache_table()?), @@ -543,6 +544,18 @@ impl<'a> Parser<'a> { }) } + pub fn parse_attach_database(&mut self) -> Result { + let database = self.parse_keyword(Keyword::DATABASE); + let database_file_name = self.parse_expr()?; + self.expect_keyword(Keyword::AS)?; + let schema_name = self.parse_identifier()?; + Ok(Statement::AttachDatabase { + database, + schema_name, + database_file_name, + }) + } + pub fn parse_analyze(&mut self) -> Result { self.expect_keyword(Keyword::TABLE)?; let table_name = self.parse_object_name()?; diff --git a/tests/sqlparser_sqlite.rs b/tests/sqlparser_sqlite.rs index fd7a22461..c4e69d530 100644 --- a/tests/sqlparser_sqlite.rs +++ b/tests/sqlparser_sqlite.rs @@ -259,6 +259,24 @@ fn parse_create_table_with_strict() { } } +#[test] +fn parse_attach_database() { + let sql = "ATTACH DATABASE 'test.db' AS test"; + let verified_stmt = sqlite().verified_stmt(sql); + assert_eq!(sql, format!("{}", verified_stmt)); + match verified_stmt { + Statement::AttachDatabase { + schema_name, + database_file_name: Expr::Value(Value::SingleQuotedString(literal_name)), + database: true, + } => { + assert_eq!(schema_name.value, "test"); + assert_eq!(literal_name, "test.db"); + } + _ => unreachable!(), + } +} + fn sqlite() -> TestedDialects { TestedDialects { dialects: vec![Box::new(SQLiteDialect {})],