Skip to content

Add support for MYSQL's RENAME TABLE #1616

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jan 1, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3401,6 +3401,13 @@ pub enum Statement {
partitioned: Option<Vec<Expr>>,
table_format: Option<HiveLoadDataFormat>,
},
/// ```sql
/// Rename TABLE tbl_name TO new_tbl_name[, tbl_name2 TO new_tbl_name2] ...
/// ```
/// renames one or more tables
///
/// See Mysql <https://dev.mysql.com/doc/refman/9.1/en/rename-table.html>
RenameTable { operations: Vec<RenameObjectDef> },
}

impl fmt::Display for Statement {
Expand Down Expand Up @@ -4940,6 +4947,9 @@ impl fmt::Display for Statement {
}
Ok(())
}
Statement::RenameTable { operations } => {
write!(f, "RENAME TABLE {}", display_comma_separated(operations))
}
}
}
}
Expand Down Expand Up @@ -7642,6 +7652,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 RenameObjectDef {
pub old_name: ObjectName,
pub new_name: ObjectName,
}

impl fmt::Display for RenameObjectDef {
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::*;
Expand Down
1 change: 1 addition & 0 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,7 @@ impl Spanned for Statement {
Statement::NOTIFY { .. } => Span::empty(),
Statement::LoadData { .. } => Span::empty(),
Statement::UNLISTEN { .. } => Span::empty(),
Statement::RenameTable { .. } => Span::empty(),
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,11 @@ pub trait Dialect: Debug + Any {
true
}

// Returns true if the dialect supports the `RENAME TABLE` statement
fn supports_rename_table(&self) -> bool {
false
}

/// Returns true if this dialect supports the `LIKE 'pattern'` option in
/// a `SHOW` statement before the `IN` option
fn supports_show_like_before_in(&self) -> bool {
Expand Down
5 changes: 5 additions & 0 deletions src/dialect/mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ impl Dialect for MySqlDialect {
fn supports_create_table_select(&self) -> bool {
true
}

/// see <https://dev.mysql.com/doc/refman/9.1/en/rename-table.html>
fn supports_rename_table(&self) -> bool {
true
}
}

/// `LOCK TABLES`
Expand Down
22 changes: 21 additions & 1 deletion src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ use IsLateral::*;
use IsOptional::*;

use crate::ast::helpers::stmt_create_table::{CreateTableBuilder, CreateTableConfiguration};
use crate::ast::Statement::CreatePolicy;
use crate::ast::*;
use crate::ast::{RenameObjectDef, Statement::CreatePolicy};
use crate::dialect::*;
use crate::keywords::{Keyword, ALL_KEYWORDS};
use crate::tokenizer::*;
Expand Down Expand Up @@ -547,6 +547,8 @@ impl<'a> Parser<'a> {
// `PRAGMA` is sqlite specific https://www.sqlite.org/pragma.html
Keyword::PRAGMA => self.parse_pragma(),
Keyword::UNLOAD => self.parse_unload(),
// `RENAME TABLE` is mysql specific https://dev.mysql.com/doc/refman/9.1/en/rename-table.html
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()
Expand Down Expand Up @@ -1044,6 +1046,16 @@ impl<'a> Parser<'a> {
Ok(Statement::NOTIFY { channel, payload })
}

pub fn parse_rename(&mut self) -> Result<Statement, ParserError> {
if self.dialect.supports_rename_table() && self.peek_keyword(Keyword::TABLE) {
self.expect_keyword(Keyword::TABLE)?;
let operations = self.parse_comma_separated(Parser::parse_rename_object_def)?;
Ok(Statement::RenameTable { operations })
} else {
self.expected("an object type 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(
Expand Down Expand Up @@ -6682,6 +6694,14 @@ impl<'a> Parser<'a> {
Ok(Tag::new(name, value))
}

pub(crate) fn parse_rename_object_def(&mut self) -> Result<RenameObjectDef, ParserError> {
let old_name = self.parse_object_name(false)?;
self.expect_keyword(Keyword::TO)?;
let new_name = self.parse_object_name(false)?;

Ok(RenameObjectDef { old_name, new_name })
}

fn parse_optional_column_option_generated(
&mut self,
) -> Result<Option<ColumnOption>, ParserError> {
Expand Down
114 changes: 114 additions & 0 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4026,6 +4026,120 @@ fn parse_alter_table() {
}
}

#[test]
fn parse_rename_table() {
let dialects = all_dialects_where(|d| d.supports_rename_table());

match dialects.verified_stmt("RENAME TABLE `test`.`test1` TO `test_db`.`test2`") {
Statement::RenameTable { operations } => {
assert_eq!(
vec![RenameObjectDef {
old_name: ObjectName(vec![
Ident {
value: "test".to_string(),
quote_style: Some('`'),
span: Span::empty(),
},
Ident {
value: "test1".to_string(),
quote_style: Some('`'),
span: Span::empty(),
}
]),
new_name: ObjectName(vec![
Ident {
value: "test_db".to_string(),
quote_style: Some('`'),
span: Span::empty(),
},
Ident {
value: "test2".to_string(),
quote_style: Some('`'),
span: Span::empty(),
}
]),
}],
operations
);
}
_ => unreachable!(),
};

match dialects.verified_stmt(
"RENAME TABLE old_table1 TO new_table1, old_table2 TO new_table2, old_table3 TO new_table3",
) {
Statement::RenameTable { operations } => {
assert_eq!(
vec![
RenameObjectDef {
old_name: ObjectName(vec![Ident {
value: "old_table1".to_string(),
quote_style: None,
span: Span::empty(),
}]),
new_name: ObjectName(vec![Ident {
value: "new_table1".to_string(),
quote_style: None,
span: Span::empty(),
}]),
},
RenameObjectDef {
old_name: ObjectName(vec![Ident {
value: "old_table2".to_string(),
quote_style: None,
span: Span::empty(),
}]),
new_name: ObjectName(vec![Ident {
value: "new_table2".to_string(),
quote_style: None,
span: Span::empty(),
}]),
},
RenameObjectDef {
old_name: ObjectName(vec![Ident {
value: "old_table3".to_string(),
quote_style: None,
span: Span::empty(),
}]),
new_name: ObjectName(vec![Ident {
value: "new_table3".to_string(),
quote_style: None,
span: Span::empty(),
}]),
}
],
operations
);
}
_ => unreachable!(),
};

assert_eq!(
dialects
.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!(
dialects
.parse_sql_statements("RENAME TABLE1 `old_table` TO new_table a")
.unwrap_err(),
ParserError::ParserError(
"Expected: an object type after RENAME, found: TABLE1".to_string()
)
);

let dialects = all_dialects_where(|d| !d.supports_rename_table());

assert_eq!(
dialects
.parse_sql_statements("RENAME TABLE `old_table` TO new_table")
.unwrap_err(),
ParserError::ParserError("Expected: an object type after RENAME, found: TABLE".to_string())
);
}

#[test]
fn test_alter_table_with_on_cluster() {
match all_dialects()
Expand Down
Loading