Support [FIRST | AFTER column_name] support in ALTER TABLE for MySQL (#1180)

This commit is contained in:
xring 2024-04-07 20:43:23 +08:00 committed by GitHub
parent 732e1ec1fc
commit 20c5754784
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 257 additions and 6 deletions

View file

@ -25,8 +25,8 @@ use sqlparser_derive::{Visit, VisitMut};
use crate::ast::value::escape_single_quote_string;
use crate::ast::{
display_comma_separated, display_separated, DataType, Expr, Ident, ObjectName, SequenceOptions,
SqlOption,
display_comma_separated, display_separated, DataType, Expr, Ident, MySQLColumnPosition,
ObjectName, SequenceOptions, SqlOption,
};
use crate::tokenizer::Token;
@ -45,6 +45,8 @@ pub enum AlterTableOperation {
if_not_exists: bool,
/// <column_def>.
column_def: ColumnDef,
/// MySQL `ALTER TABLE` only [FIRST | AFTER column_name]
column_position: Option<MySQLColumnPosition>,
},
/// `DISABLE ROW LEVEL SECURITY`
///
@ -129,6 +131,8 @@ pub enum AlterTableOperation {
new_name: Ident,
data_type: DataType,
options: Vec<ColumnOption>,
/// MySQL `ALTER TABLE` only [FIRST | AFTER column_name]
column_position: Option<MySQLColumnPosition>,
},
/// `RENAME CONSTRAINT <old_constraint_name> TO <new_constraint_name>`
///
@ -171,6 +175,7 @@ impl fmt::Display for AlterTableOperation {
column_keyword,
if_not_exists,
column_def,
column_position,
} => {
write!(f, "ADD")?;
if *column_keyword {
@ -181,6 +186,10 @@ impl fmt::Display for AlterTableOperation {
}
write!(f, " {column_def}")?;
if let Some(position) = column_position {
write!(f, " {position}")?;
}
Ok(())
}
AlterTableOperation::AlterColumn { column_name, op } => {
@ -271,13 +280,17 @@ impl fmt::Display for AlterTableOperation {
new_name,
data_type,
options,
column_position,
} => {
write!(f, "CHANGE COLUMN {old_name} {new_name} {data_type}")?;
if options.is_empty() {
Ok(())
} else {
write!(f, " {}", display_separated(options, " "))
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}")

View file

@ -6018,6 +6018,28 @@ impl fmt::Display for HiveSetLocation {
}
}
/// MySQL `ALTER TABLE` only [FIRST | AFTER column_name]
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum MySQLColumnPosition {
First,
After(Ident),
}
impl Display for MySQLColumnPosition {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
MySQLColumnPosition::First => Ok(write!(f, "FIRST")?),
MySQLColumnPosition::After(ident) => {
let column_name = &ident.value;
Ok(write!(f, "AFTER {column_name}")?)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;

View file

@ -73,6 +73,7 @@ define_keywords!(
ACTION,
ADD,
ADMIN,
AFTER,
AGAINST,
ALL,
ALLOCATE,

View file

@ -5358,10 +5358,14 @@ impl<'a> Parser<'a> {
};
let column_def = self.parse_column_def()?;
let column_position = self.parse_column_position()?;
AlterTableOperation::AddColumn {
column_keyword,
if_not_exists,
column_def,
column_position,
}
}
}
@ -5490,11 +5494,14 @@ impl<'a> Parser<'a> {
options.push(option);
}
let column_position = self.parse_column_position()?;
AlterTableOperation::ChangeColumn {
old_name,
new_name,
data_type,
options,
column_position,
}
} else if self.parse_keyword(Keyword::ALTER) {
let _ = self.parse_keyword(Keyword::COLUMN); // [ COLUMN ]
@ -9608,6 +9615,21 @@ impl<'a> Parser<'a> {
Ok(partitions)
}
fn parse_column_position(&mut self) -> Result<Option<MySQLColumnPosition>, ParserError> {
if dialect_of!(self is MySqlDialect | GenericDialect) {
if self.parse_keyword(Keyword::FIRST) {
Ok(Some(MySQLColumnPosition::First))
} else if self.parse_keyword(Keyword::AFTER) {
let ident = self.parse_identifier(false)?;
Ok(Some(MySQLColumnPosition::After(ident)))
} else {
Ok(None)
}
} else {
Ok(None)
}
}
/// Consume the parser and return its underlying token buffer
pub fn into_tokens(self) -> Vec<TokenWithLocation> {
self.tokens

View file

@ -3512,11 +3512,13 @@ fn parse_alter_table() {
column_keyword,
if_not_exists,
column_def,
column_position,
} => {
assert!(column_keyword);
assert!(!if_not_exists);
assert_eq!("foo", column_def.name.to_string());
assert_eq!("TEXT", column_def.data_type.to_string());
assert_eq!(None, column_position);
}
_ => unreachable!(),
};

View file

@ -1875,6 +1875,120 @@ fn parse_delete_with_limit() {
}
}
#[test]
fn parse_alter_table_add_column() {
match mysql().verified_stmt("ALTER TABLE tab ADD COLUMN b INT FIRST") {
Statement::AlterTable {
name,
if_exists,
only,
operations,
location: _,
} => {
assert_eq!(name.to_string(), "tab");
assert!(!if_exists);
assert!(!only);
assert_eq!(
operations,
vec![AlterTableOperation::AddColumn {
column_keyword: true,
if_not_exists: false,
column_def: ColumnDef {
name: "b".into(),
data_type: DataType::Int(None),
collation: None,
options: vec![],
},
column_position: Some(MySQLColumnPosition::First),
},]
);
}
_ => unreachable!(),
}
match mysql().verified_stmt("ALTER TABLE tab ADD COLUMN b INT AFTER foo") {
Statement::AlterTable {
name,
if_exists,
only,
operations,
location: _,
} => {
assert_eq!(name.to_string(), "tab");
assert!(!if_exists);
assert!(!only);
assert_eq!(
operations,
vec![AlterTableOperation::AddColumn {
column_keyword: true,
if_not_exists: false,
column_def: ColumnDef {
name: "b".into(),
data_type: DataType::Int(None),
collation: None,
options: vec![],
},
column_position: Some(MySQLColumnPosition::After(Ident {
value: String::from("foo"),
quote_style: None
})),
},]
);
}
_ => unreachable!(),
}
}
#[test]
fn parse_alter_table_add_columns() {
match mysql()
.verified_stmt("ALTER TABLE tab ADD COLUMN a TEXT FIRST, ADD COLUMN b INT AFTER foo")
{
Statement::AlterTable {
name,
if_exists,
only,
operations,
location: _,
} => {
assert_eq!(name.to_string(), "tab");
assert!(!if_exists);
assert!(!only);
assert_eq!(
operations,
vec![
AlterTableOperation::AddColumn {
column_keyword: true,
if_not_exists: false,
column_def: ColumnDef {
name: "a".into(),
data_type: DataType::Text,
collation: None,
options: vec![],
},
column_position: Some(MySQLColumnPosition::First),
},
AlterTableOperation::AddColumn {
column_keyword: true,
if_not_exists: false,
column_def: ColumnDef {
name: "b".into(),
data_type: DataType::Int(None),
collation: None,
options: vec![],
},
column_position: Some(MySQLColumnPosition::After(Ident {
value: String::from("foo"),
quote_style: None,
})),
},
]
);
}
_ => unreachable!(),
}
}
#[test]
fn parse_alter_table_drop_primary_key() {
assert_matches!(
@ -1891,6 +2005,7 @@ fn parse_alter_table_change_column() {
new_name: Ident::new("desc"),
data_type: DataType::Text,
options: vec![ColumnOption::NotNull],
column_position: None,
};
let sql1 = "ALTER TABLE orders CHANGE COLUMN description desc TEXT NOT NULL";
@ -1904,6 +2019,80 @@ fn parse_alter_table_change_column() {
&expected_name.to_string(),
);
assert_eq!(expected_operation, operation);
let expected_operation = AlterTableOperation::ChangeColumn {
old_name: Ident::new("description"),
new_name: Ident::new("desc"),
data_type: DataType::Text,
options: vec![ColumnOption::NotNull],
column_position: Some(MySQLColumnPosition::First),
};
let sql3 = "ALTER TABLE orders CHANGE COLUMN description desc 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::ChangeColumn {
old_name: Ident::new("description"),
new_name: Ident::new("desc"),
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 CHANGE COLUMN description desc 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_change_column_with_column_position() {
let expected_name = ObjectName(vec![Ident::new("orders")]);
let expected_operation_first = AlterTableOperation::ChangeColumn {
old_name: Ident::new("description"),
new_name: Ident::new("desc"),
data_type: DataType::Text,
options: vec![ColumnOption::NotNull],
column_position: Some(MySQLColumnPosition::First),
};
let sql1 = "ALTER TABLE orders CHANGE COLUMN description desc 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 CHANGE description desc 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::ChangeColumn {
old_name: Ident::new("description"),
new_name: Ident::new("desc"),
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 CHANGE COLUMN description desc 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 CHANGE description desc 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]

View file

@ -694,6 +694,7 @@ fn parse_alter_table_add_columns() {
collation: None,
options: vec![],
},
column_position: None,
},
AlterTableOperation::AddColumn {
column_keyword: true,
@ -704,6 +705,7 @@ fn parse_alter_table_add_columns() {
collation: None,
options: vec![],
},
column_position: None,
},
]
);