From 7b49c69b3a14ba3a2763dd4c9b33913e6e5649ca Mon Sep 17 00:00:00 2001 From: Kould Date: Sun, 21 Apr 2024 20:32:53 +0800 Subject: [PATCH] Support `Modify Column` for MySQL dialect (#1216) --- src/ast/ddl.rs | 24 +++++++++++ src/keywords.rs | 1 + src/parser/mod.rs | 17 ++++++++ tests/sqlparser_mysql.rs | 93 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 135 insertions(+) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index d86ebad9d..de514550b 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -134,6 +134,14 @@ pub enum AlterTableOperation { /// MySQL `ALTER TABLE` only [FIRST | AFTER column_name] column_position: Option, }, + // CHANGE [ COLUMN ] [ ] + ModifyColumn { + col_name: Ident, + data_type: DataType, + options: Vec, + /// MySQL `ALTER TABLE` only [FIRST | AFTER column_name] + column_position: Option, + }, /// `RENAME CONSTRAINT TO ` /// /// Note: this is a PostgreSQL-specific operation. @@ -292,6 +300,22 @@ impl fmt::Display for AlterTableOperation { Ok(()) } + AlterTableOperation::ModifyColumn { + col_name, + data_type, + options, + column_position, + } => { + write!(f, "MODIFY COLUMN {col_name} {data_type}")?; + if !options.is_empty() { + write!(f, " {}", display_separated(options, " "))?; + } + if let Some(position) = column_position { + write!(f, " {position}")?; + } + + Ok(()) + } AlterTableOperation::RenameConstraint { old_name, new_name } => { write!(f, "RENAME CONSTRAINT {old_name} TO {new_name}") } diff --git a/src/keywords.rs b/src/keywords.rs index 12a376b2a..fcc344bcd 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -438,6 +438,7 @@ define_keywords!( MOD, MODE, MODIFIES, + MODIFY, MODULE, MONTH, MSCK, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 6779dfd0f..9910c889a 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -5757,6 +5757,23 @@ impl<'a> Parser<'a> { options, column_position, } + } else if self.parse_keyword(Keyword::MODIFY) { + let _ = self.parse_keyword(Keyword::COLUMN); // [ COLUMN ] + let col_name = self.parse_identifier(false)?; + let data_type = self.parse_data_type()?; + let mut options = vec![]; + while let Some(option) = self.parse_optional_column_option()? { + options.push(option); + } + + let column_position = self.parse_column_position()?; + + AlterTableOperation::ModifyColumn { + col_name, + data_type, + options, + column_position, + } } else if self.parse_keyword(Keyword::ALTER) { let _ = self.parse_keyword(Keyword::COLUMN); // [ COLUMN ] let column_name = self.parse_identifier(false)?; diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 5f64079a6..e53f434d5 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -2218,6 +2218,99 @@ fn parse_alter_table_change_column_with_column_position() { assert_eq!(expected_operation_after, operation); } +#[test] +fn parse_alter_table_modify_column() { + let expected_name = ObjectName(vec![Ident::new("orders")]); + let expected_operation = AlterTableOperation::ModifyColumn { + col_name: Ident::new("description"), + data_type: DataType::Text, + options: vec![ColumnOption::NotNull], + column_position: None, + }; + + let sql1 = "ALTER TABLE orders MODIFY COLUMN description TEXT NOT NULL"; + let operation = + alter_table_op_with_name(mysql().verified_stmt(sql1), &expected_name.to_string()); + assert_eq!(expected_operation, operation); + + let sql2 = "ALTER TABLE orders MODIFY description TEXT NOT NULL"; + let operation = alter_table_op_with_name( + mysql().one_statement_parses_to(sql2, sql1), + &expected_name.to_string(), + ); + assert_eq!(expected_operation, operation); + + let expected_operation = AlterTableOperation::ModifyColumn { + col_name: Ident::new("description"), + data_type: DataType::Text, + options: vec![ColumnOption::NotNull], + column_position: Some(MySQLColumnPosition::First), + }; + let sql3 = "ALTER TABLE orders MODIFY COLUMN description TEXT NOT NULL FIRST"; + let operation = + alter_table_op_with_name(mysql().verified_stmt(sql3), &expected_name.to_string()); + assert_eq!(expected_operation, operation); + + let expected_operation = AlterTableOperation::ModifyColumn { + col_name: Ident::new("description"), + data_type: DataType::Text, + options: vec![ColumnOption::NotNull], + column_position: Some(MySQLColumnPosition::After(Ident { + value: String::from("foo"), + quote_style: None, + })), + }; + let sql4 = "ALTER TABLE orders MODIFY COLUMN description TEXT NOT NULL AFTER foo"; + let operation = + alter_table_op_with_name(mysql().verified_stmt(sql4), &expected_name.to_string()); + assert_eq!(expected_operation, operation); +} + +#[test] +fn parse_alter_table_modify_column_with_column_position() { + let expected_name = ObjectName(vec![Ident::new("orders")]); + let expected_operation_first = AlterTableOperation::ModifyColumn { + col_name: Ident::new("description"), + data_type: DataType::Text, + options: vec![ColumnOption::NotNull], + column_position: Some(MySQLColumnPosition::First), + }; + + let sql1 = "ALTER TABLE orders MODIFY COLUMN description TEXT NOT NULL FIRST"; + let operation = + alter_table_op_with_name(mysql().verified_stmt(sql1), &expected_name.to_string()); + assert_eq!(expected_operation_first, operation); + + let sql2 = "ALTER TABLE orders MODIFY description TEXT NOT NULL FIRST"; + let operation = alter_table_op_with_name( + mysql().one_statement_parses_to(sql2, sql1), + &expected_name.to_string(), + ); + assert_eq!(expected_operation_first, operation); + + let expected_operation_after = AlterTableOperation::ModifyColumn { + col_name: Ident::new("description"), + data_type: DataType::Text, + options: vec![ColumnOption::NotNull], + column_position: Some(MySQLColumnPosition::After(Ident { + value: String::from("total_count"), + quote_style: None, + })), + }; + + let sql1 = "ALTER TABLE orders MODIFY COLUMN description TEXT NOT NULL AFTER total_count"; + let operation = + alter_table_op_with_name(mysql().verified_stmt(sql1), &expected_name.to_string()); + assert_eq!(expected_operation_after, operation); + + let sql2 = "ALTER TABLE orders MODIFY description TEXT NOT NULL AFTER total_count"; + let operation = alter_table_op_with_name( + mysql().one_statement_parses_to(sql2, sql1), + &expected_name.to_string(), + ); + assert_eq!(expected_operation_after, operation); +} + #[test] fn parse_substring_in_select() { let sql = "SELECT DISTINCT SUBSTRING(description, 0, 1) FROM test";