mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-04 06:18:17 +00:00
Support [FIRST | AFTER column_name]
support in ALTER TABLE
for MySQL (#1180)
This commit is contained in:
parent
732e1ec1fc
commit
20c5754784
7 changed files with 257 additions and 6 deletions
|
@ -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}")
|
||||
|
|
|
@ -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::*;
|
||||
|
|
|
@ -73,6 +73,7 @@ define_keywords!(
|
|||
ACTION,
|
||||
ADD,
|
||||
ADMIN,
|
||||
AFTER,
|
||||
AGAINST,
|
||||
ALL,
|
||||
ALLOCATE,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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!(),
|
||||
};
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
]
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue