mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-29 18:34:04 +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::value::escape_single_quote_string;
|
||||||
use crate::ast::{
|
use crate::ast::{
|
||||||
display_comma_separated, display_separated, DataType, Expr, Ident, ObjectName, SequenceOptions,
|
display_comma_separated, display_separated, DataType, Expr, Ident, MySQLColumnPosition,
|
||||||
SqlOption,
|
ObjectName, SequenceOptions, SqlOption,
|
||||||
};
|
};
|
||||||
use crate::tokenizer::Token;
|
use crate::tokenizer::Token;
|
||||||
|
|
||||||
|
@ -45,6 +45,8 @@ pub enum AlterTableOperation {
|
||||||
if_not_exists: bool,
|
if_not_exists: bool,
|
||||||
/// <column_def>.
|
/// <column_def>.
|
||||||
column_def: ColumnDef,
|
column_def: ColumnDef,
|
||||||
|
/// MySQL `ALTER TABLE` only [FIRST | AFTER column_name]
|
||||||
|
column_position: Option<MySQLColumnPosition>,
|
||||||
},
|
},
|
||||||
/// `DISABLE ROW LEVEL SECURITY`
|
/// `DISABLE ROW LEVEL SECURITY`
|
||||||
///
|
///
|
||||||
|
@ -129,6 +131,8 @@ pub enum AlterTableOperation {
|
||||||
new_name: Ident,
|
new_name: Ident,
|
||||||
data_type: DataType,
|
data_type: DataType,
|
||||||
options: Vec<ColumnOption>,
|
options: Vec<ColumnOption>,
|
||||||
|
/// MySQL `ALTER TABLE` only [FIRST | AFTER column_name]
|
||||||
|
column_position: Option<MySQLColumnPosition>,
|
||||||
},
|
},
|
||||||
/// `RENAME CONSTRAINT <old_constraint_name> TO <new_constraint_name>`
|
/// `RENAME CONSTRAINT <old_constraint_name> TO <new_constraint_name>`
|
||||||
///
|
///
|
||||||
|
@ -171,6 +175,7 @@ impl fmt::Display for AlterTableOperation {
|
||||||
column_keyword,
|
column_keyword,
|
||||||
if_not_exists,
|
if_not_exists,
|
||||||
column_def,
|
column_def,
|
||||||
|
column_position,
|
||||||
} => {
|
} => {
|
||||||
write!(f, "ADD")?;
|
write!(f, "ADD")?;
|
||||||
if *column_keyword {
|
if *column_keyword {
|
||||||
|
@ -181,6 +186,10 @@ impl fmt::Display for AlterTableOperation {
|
||||||
}
|
}
|
||||||
write!(f, " {column_def}")?;
|
write!(f, " {column_def}")?;
|
||||||
|
|
||||||
|
if let Some(position) = column_position {
|
||||||
|
write!(f, " {position}")?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
AlterTableOperation::AlterColumn { column_name, op } => {
|
AlterTableOperation::AlterColumn { column_name, op } => {
|
||||||
|
@ -271,13 +280,17 @@ impl fmt::Display for AlterTableOperation {
|
||||||
new_name,
|
new_name,
|
||||||
data_type,
|
data_type,
|
||||||
options,
|
options,
|
||||||
|
column_position,
|
||||||
} => {
|
} => {
|
||||||
write!(f, "CHANGE COLUMN {old_name} {new_name} {data_type}")?;
|
write!(f, "CHANGE COLUMN {old_name} {new_name} {data_type}")?;
|
||||||
if options.is_empty() {
|
if !options.is_empty() {
|
||||||
Ok(())
|
write!(f, " {}", display_separated(options, " "))?;
|
||||||
} else {
|
|
||||||
write!(f, " {}", display_separated(options, " "))
|
|
||||||
}
|
}
|
||||||
|
if let Some(position) = column_position {
|
||||||
|
write!(f, " {position}")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
AlterTableOperation::RenameConstraint { old_name, new_name } => {
|
AlterTableOperation::RenameConstraint { old_name, new_name } => {
|
||||||
write!(f, "RENAME CONSTRAINT {old_name} TO {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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -73,6 +73,7 @@ define_keywords!(
|
||||||
ACTION,
|
ACTION,
|
||||||
ADD,
|
ADD,
|
||||||
ADMIN,
|
ADMIN,
|
||||||
|
AFTER,
|
||||||
AGAINST,
|
AGAINST,
|
||||||
ALL,
|
ALL,
|
||||||
ALLOCATE,
|
ALLOCATE,
|
||||||
|
|
|
@ -5358,10 +5358,14 @@ impl<'a> Parser<'a> {
|
||||||
};
|
};
|
||||||
|
|
||||||
let column_def = self.parse_column_def()?;
|
let column_def = self.parse_column_def()?;
|
||||||
|
|
||||||
|
let column_position = self.parse_column_position()?;
|
||||||
|
|
||||||
AlterTableOperation::AddColumn {
|
AlterTableOperation::AddColumn {
|
||||||
column_keyword,
|
column_keyword,
|
||||||
if_not_exists,
|
if_not_exists,
|
||||||
column_def,
|
column_def,
|
||||||
|
column_position,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5490,11 +5494,14 @@ impl<'a> Parser<'a> {
|
||||||
options.push(option);
|
options.push(option);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let column_position = self.parse_column_position()?;
|
||||||
|
|
||||||
AlterTableOperation::ChangeColumn {
|
AlterTableOperation::ChangeColumn {
|
||||||
old_name,
|
old_name,
|
||||||
new_name,
|
new_name,
|
||||||
data_type,
|
data_type,
|
||||||
options,
|
options,
|
||||||
|
column_position,
|
||||||
}
|
}
|
||||||
} else if self.parse_keyword(Keyword::ALTER) {
|
} else if self.parse_keyword(Keyword::ALTER) {
|
||||||
let _ = self.parse_keyword(Keyword::COLUMN); // [ COLUMN ]
|
let _ = self.parse_keyword(Keyword::COLUMN); // [ COLUMN ]
|
||||||
|
@ -9608,6 +9615,21 @@ impl<'a> Parser<'a> {
|
||||||
Ok(partitions)
|
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
|
/// Consume the parser and return its underlying token buffer
|
||||||
pub fn into_tokens(self) -> Vec<TokenWithLocation> {
|
pub fn into_tokens(self) -> Vec<TokenWithLocation> {
|
||||||
self.tokens
|
self.tokens
|
||||||
|
|
|
@ -3512,11 +3512,13 @@ fn parse_alter_table() {
|
||||||
column_keyword,
|
column_keyword,
|
||||||
if_not_exists,
|
if_not_exists,
|
||||||
column_def,
|
column_def,
|
||||||
|
column_position,
|
||||||
} => {
|
} => {
|
||||||
assert!(column_keyword);
|
assert!(column_keyword);
|
||||||
assert!(!if_not_exists);
|
assert!(!if_not_exists);
|
||||||
assert_eq!("foo", column_def.name.to_string());
|
assert_eq!("foo", column_def.name.to_string());
|
||||||
assert_eq!("TEXT", column_def.data_type.to_string());
|
assert_eq!("TEXT", column_def.data_type.to_string());
|
||||||
|
assert_eq!(None, column_position);
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => 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]
|
#[test]
|
||||||
fn parse_alter_table_drop_primary_key() {
|
fn parse_alter_table_drop_primary_key() {
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
|
@ -1891,6 +2005,7 @@ fn parse_alter_table_change_column() {
|
||||||
new_name: Ident::new("desc"),
|
new_name: Ident::new("desc"),
|
||||||
data_type: DataType::Text,
|
data_type: DataType::Text,
|
||||||
options: vec![ColumnOption::NotNull],
|
options: vec![ColumnOption::NotNull],
|
||||||
|
column_position: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let sql1 = "ALTER TABLE orders CHANGE COLUMN description desc TEXT NOT NULL";
|
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(),
|
&expected_name.to_string(),
|
||||||
);
|
);
|
||||||
assert_eq!(expected_operation, operation);
|
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]
|
#[test]
|
||||||
|
|
|
@ -694,6 +694,7 @@ fn parse_alter_table_add_columns() {
|
||||||
collation: None,
|
collation: None,
|
||||||
options: vec![],
|
options: vec![],
|
||||||
},
|
},
|
||||||
|
column_position: None,
|
||||||
},
|
},
|
||||||
AlterTableOperation::AddColumn {
|
AlterTableOperation::AddColumn {
|
||||||
column_keyword: true,
|
column_keyword: true,
|
||||||
|
@ -704,6 +705,7 @@ fn parse_alter_table_add_columns() {
|
||||||
collation: None,
|
collation: None,
|
||||||
options: vec![],
|
options: vec![],
|
||||||
},
|
},
|
||||||
|
column_position: None,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue