From 94ea20628fbc9950e87d165e3b4f904419ec03f0 Mon Sep 17 00:00:00 2001 From: wugeer <1284057728@qq.com> Date: Thu, 2 Jan 2025 04:54:58 +0800 Subject: [PATCH] Add support for MYSQL's `RENAME TABLE` (#1616) Co-authored-by: Ifeanyi Ubah --- src/ast/mod.rs | 26 +++++++++++++++++ src/ast/spans.rs | 1 + src/parser/mod.rs | 18 ++++++++++++ tests/sqlparser_common.rs | 59 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index d99f68886..756774353 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -3413,6 +3413,13 @@ pub enum Statement { partitioned: Option>, table_format: Option, }, + /// ```sql + /// Rename TABLE tbl_name TO new_tbl_name[, tbl_name2 TO new_tbl_name2] ... + /// ``` + /// Renames one or more tables + /// + /// See Mysql + RenameTable(Vec), } impl fmt::Display for Statement { @@ -4970,6 +4977,9 @@ impl fmt::Display for Statement { } Ok(()) } + Statement::RenameTable(rename_tables) => { + write!(f, "RENAME TABLE {}", display_comma_separated(rename_tables)) + } } } } @@ -7672,6 +7682,22 @@ impl Display for JsonNullClause { } } +/// rename object definition +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct RenameTable { + pub old_name: ObjectName, + pub new_name: ObjectName, +} + +impl fmt::Display for RenameTable { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} TO {}", self.old_name, self.new_name)?; + Ok(()) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 574830ef5..dad0c5379 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -492,6 +492,7 @@ impl Spanned for Statement { Statement::NOTIFY { .. } => Span::empty(), Statement::LoadData { .. } => Span::empty(), Statement::UNLISTEN { .. } => Span::empty(), + Statement::RenameTable { .. } => Span::empty(), } } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 012314b42..47d4d6f0d 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -595,6 +595,7 @@ impl<'a> Parser<'a> { // `PRAGMA` is sqlite specific https://www.sqlite.org/pragma.html Keyword::PRAGMA => self.parse_pragma(), Keyword::UNLOAD => self.parse_unload(), + Keyword::RENAME => self.parse_rename(), // `INSTALL` is duckdb specific https://duckdb.org/docs/extensions/overview Keyword::INSTALL if dialect_of!(self is DuckDbDialect | GenericDialect) => { self.parse_install() @@ -1085,6 +1086,23 @@ impl<'a> Parser<'a> { Ok(Statement::NOTIFY { channel, payload }) } + /// Parses a `RENAME TABLE` statement. See [Statement::RenameTable] + pub fn parse_rename(&mut self) -> Result { + if self.peek_keyword(Keyword::TABLE) { + self.expect_keyword(Keyword::TABLE)?; + let rename_tables = self.parse_comma_separated(|parser| { + let old_name = parser.parse_object_name(false)?; + parser.expect_keyword(Keyword::TO)?; + let new_name = parser.parse_object_name(false)?; + + Ok(RenameTable { old_name, new_name }) + })?; + Ok(Statement::RenameTable(rename_tables)) + } else { + self.expected("KEYWORD `TABLE` after RENAME", self.peek_token()) + } + } + // Tries to parse an expression by matching the specified word to known keywords that have a special meaning in the dialect. // Returns `None if no match is found. fn parse_expr_prefix_by_reserved_word( diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 3b21160b9..3c2e0899f 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -4133,6 +4133,65 @@ fn parse_alter_table() { } } +#[test] +fn parse_rename_table() { + match verified_stmt("RENAME TABLE test.test1 TO test_db.test2") { + Statement::RenameTable(rename_tables) => { + assert_eq!( + vec![RenameTable { + old_name: ObjectName(vec![ + Ident::new("test".to_string()), + Ident::new("test1".to_string()), + ]), + new_name: ObjectName(vec![ + Ident::new("test_db".to_string()), + Ident::new("test2".to_string()), + ]), + }], + rename_tables + ); + } + _ => unreachable!(), + }; + + match verified_stmt( + "RENAME TABLE old_table1 TO new_table1, old_table2 TO new_table2, old_table3 TO new_table3", + ) { + Statement::RenameTable(rename_tables) => { + assert_eq!( + vec![ + RenameTable { + old_name: ObjectName(vec![Ident::new("old_table1".to_string())]), + new_name: ObjectName(vec![Ident::new("new_table1".to_string())]), + }, + RenameTable { + old_name: ObjectName(vec![Ident::new("old_table2".to_string())]), + new_name: ObjectName(vec![Ident::new("new_table2".to_string())]), + }, + RenameTable { + old_name: ObjectName(vec![Ident::new("old_table3".to_string())]), + new_name: ObjectName(vec![Ident::new("new_table3".to_string())]), + } + ], + rename_tables + ); + } + _ => unreachable!(), + }; + + assert_eq!( + parse_sql_statements("RENAME TABLE old_table TO new_table a").unwrap_err(), + ParserError::ParserError("Expected: end of statement, found: a".to_string()) + ); + + assert_eq!( + parse_sql_statements("RENAME TABLE1 old_table TO new_table a").unwrap_err(), + ParserError::ParserError( + "Expected: KEYWORD `TABLE` after RENAME, found: TABLE1".to_string() + ) + ); +} + #[test] fn test_alter_table_with_on_cluster() { match all_dialects()