Skip to content

Commit 20c5754

Browse files
authored
Support [FIRST | AFTER column_name] support in ALTER TABLE for MySQL (apache#1180)
1 parent 732e1ec commit 20c5754

7 files changed

Lines changed: 257 additions & 6 deletions

File tree

src/ast/ddl.rs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ use sqlparser_derive::{Visit, VisitMut};
2525

2626
use crate::ast::value::escape_single_quote_string;
2727
use crate::ast::{
28-
display_comma_separated, display_separated, DataType, Expr, Ident, ObjectName, SequenceOptions,
29-
SqlOption,
28+
display_comma_separated, display_separated, DataType, Expr, Ident, MySQLColumnPosition,
29+
ObjectName, SequenceOptions, SqlOption,
3030
};
3131
use crate::tokenizer::Token;
3232

@@ -45,6 +45,8 @@ pub enum AlterTableOperation {
4545
if_not_exists: bool,
4646
/// <column_def>.
4747
column_def: ColumnDef,
48+
/// MySQL `ALTER TABLE` only [FIRST | AFTER column_name]
49+
column_position: Option<MySQLColumnPosition>,
4850
},
4951
/// `DISABLE ROW LEVEL SECURITY`
5052
///
@@ -129,6 +131,8 @@ pub enum AlterTableOperation {
129131
new_name: Ident,
130132
data_type: DataType,
131133
options: Vec<ColumnOption>,
134+
/// MySQL `ALTER TABLE` only [FIRST | AFTER column_name]
135+
column_position: Option<MySQLColumnPosition>,
132136
},
133137
/// `RENAME CONSTRAINT <old_constraint_name> TO <new_constraint_name>`
134138
///
@@ -171,6 +175,7 @@ impl fmt::Display for AlterTableOperation {
171175
column_keyword,
172176
if_not_exists,
173177
column_def,
178+
column_position,
174179
} => {
175180
write!(f, "ADD")?;
176181
if *column_keyword {
@@ -181,6 +186,10 @@ impl fmt::Display for AlterTableOperation {
181186
}
182187
write!(f, " {column_def}")?;
183188

189+
if let Some(position) = column_position {
190+
write!(f, " {position}")?;
191+
}
192+
184193
Ok(())
185194
}
186195
AlterTableOperation::AlterColumn { column_name, op } => {
@@ -271,13 +280,17 @@ impl fmt::Display for AlterTableOperation {
271280
new_name,
272281
data_type,
273282
options,
283+
column_position,
274284
} => {
275285
write!(f, "CHANGE COLUMN {old_name} {new_name} {data_type}")?;
276-
if options.is_empty() {
277-
Ok(())
278-
} else {
279-
write!(f, " {}", display_separated(options, " "))
286+
if !options.is_empty() {
287+
write!(f, " {}", display_separated(options, " "))?;
280288
}
289+
if let Some(position) = column_position {
290+
write!(f, " {position}")?;
291+
}
292+
293+
Ok(())
281294
}
282295
AlterTableOperation::RenameConstraint { old_name, new_name } => {
283296
write!(f, "RENAME CONSTRAINT {old_name} TO {new_name}")

src/ast/mod.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6018,6 +6018,28 @@ impl fmt::Display for HiveSetLocation {
60186018
}
60196019
}
60206020

6021+
/// MySQL `ALTER TABLE` only [FIRST | AFTER column_name]
6022+
#[allow(clippy::large_enum_variant)]
6023+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
6024+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
6025+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
6026+
pub enum MySQLColumnPosition {
6027+
First,
6028+
After(Ident),
6029+
}
6030+
6031+
impl Display for MySQLColumnPosition {
6032+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
6033+
match self {
6034+
MySQLColumnPosition::First => Ok(write!(f, "FIRST")?),
6035+
MySQLColumnPosition::After(ident) => {
6036+
let column_name = &ident.value;
6037+
Ok(write!(f, "AFTER {column_name}")?)
6038+
}
6039+
}
6040+
}
6041+
}
6042+
60216043
#[cfg(test)]
60226044
mod tests {
60236045
use super::*;

src/keywords.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ define_keywords!(
7373
ACTION,
7474
ADD,
7575
ADMIN,
76+
AFTER,
7677
AGAINST,
7778
ALL,
7879
ALLOCATE,

src/parser/mod.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5358,10 +5358,14 @@ impl<'a> Parser<'a> {
53585358
};
53595359

53605360
let column_def = self.parse_column_def()?;
5361+
5362+
let column_position = self.parse_column_position()?;
5363+
53615364
AlterTableOperation::AddColumn {
53625365
column_keyword,
53635366
if_not_exists,
53645367
column_def,
5368+
column_position,
53655369
}
53665370
}
53675371
}
@@ -5490,11 +5494,14 @@ impl<'a> Parser<'a> {
54905494
options.push(option);
54915495
}
54925496

5497+
let column_position = self.parse_column_position()?;
5498+
54935499
AlterTableOperation::ChangeColumn {
54945500
old_name,
54955501
new_name,
54965502
data_type,
54975503
options,
5504+
column_position,
54985505
}
54995506
} else if self.parse_keyword(Keyword::ALTER) {
55005507
let _ = self.parse_keyword(Keyword::COLUMN); // [ COLUMN ]
@@ -9608,6 +9615,21 @@ impl<'a> Parser<'a> {
96089615
Ok(partitions)
96099616
}
96109617

9618+
fn parse_column_position(&mut self) -> Result<Option<MySQLColumnPosition>, ParserError> {
9619+
if dialect_of!(self is MySqlDialect | GenericDialect) {
9620+
if self.parse_keyword(Keyword::FIRST) {
9621+
Ok(Some(MySQLColumnPosition::First))
9622+
} else if self.parse_keyword(Keyword::AFTER) {
9623+
let ident = self.parse_identifier(false)?;
9624+
Ok(Some(MySQLColumnPosition::After(ident)))
9625+
} else {
9626+
Ok(None)
9627+
}
9628+
} else {
9629+
Ok(None)
9630+
}
9631+
}
9632+
96119633
/// Consume the parser and return its underlying token buffer
96129634
pub fn into_tokens(self) -> Vec<TokenWithLocation> {
96139635
self.tokens

tests/sqlparser_common.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3512,11 +3512,13 @@ fn parse_alter_table() {
35123512
column_keyword,
35133513
if_not_exists,
35143514
column_def,
3515+
column_position,
35153516
} => {
35163517
assert!(column_keyword);
35173518
assert!(!if_not_exists);
35183519
assert_eq!("foo", column_def.name.to_string());
35193520
assert_eq!("TEXT", column_def.data_type.to_string());
3521+
assert_eq!(None, column_position);
35203522
}
35213523
_ => unreachable!(),
35223524
};

tests/sqlparser_mysql.rs

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1875,6 +1875,120 @@ fn parse_delete_with_limit() {
18751875
}
18761876
}
18771877

1878+
#[test]
1879+
fn parse_alter_table_add_column() {
1880+
match mysql().verified_stmt("ALTER TABLE tab ADD COLUMN b INT FIRST") {
1881+
Statement::AlterTable {
1882+
name,
1883+
if_exists,
1884+
only,
1885+
operations,
1886+
location: _,
1887+
} => {
1888+
assert_eq!(name.to_string(), "tab");
1889+
assert!(!if_exists);
1890+
assert!(!only);
1891+
assert_eq!(
1892+
operations,
1893+
vec![AlterTableOperation::AddColumn {
1894+
column_keyword: true,
1895+
if_not_exists: false,
1896+
column_def: ColumnDef {
1897+
name: "b".into(),
1898+
data_type: DataType::Int(None),
1899+
collation: None,
1900+
options: vec![],
1901+
},
1902+
column_position: Some(MySQLColumnPosition::First),
1903+
},]
1904+
);
1905+
}
1906+
_ => unreachable!(),
1907+
}
1908+
1909+
match mysql().verified_stmt("ALTER TABLE tab ADD COLUMN b INT AFTER foo") {
1910+
Statement::AlterTable {
1911+
name,
1912+
if_exists,
1913+
only,
1914+
operations,
1915+
location: _,
1916+
} => {
1917+
assert_eq!(name.to_string(), "tab");
1918+
assert!(!if_exists);
1919+
assert!(!only);
1920+
assert_eq!(
1921+
operations,
1922+
vec![AlterTableOperation::AddColumn {
1923+
column_keyword: true,
1924+
if_not_exists: false,
1925+
column_def: ColumnDef {
1926+
name: "b".into(),
1927+
data_type: DataType::Int(None),
1928+
collation: None,
1929+
options: vec![],
1930+
},
1931+
column_position: Some(MySQLColumnPosition::After(Ident {
1932+
value: String::from("foo"),
1933+
quote_style: None
1934+
})),
1935+
},]
1936+
);
1937+
}
1938+
_ => unreachable!(),
1939+
}
1940+
}
1941+
1942+
#[test]
1943+
fn parse_alter_table_add_columns() {
1944+
match mysql()
1945+
.verified_stmt("ALTER TABLE tab ADD COLUMN a TEXT FIRST, ADD COLUMN b INT AFTER foo")
1946+
{
1947+
Statement::AlterTable {
1948+
name,
1949+
if_exists,
1950+
only,
1951+
operations,
1952+
location: _,
1953+
} => {
1954+
assert_eq!(name.to_string(), "tab");
1955+
assert!(!if_exists);
1956+
assert!(!only);
1957+
assert_eq!(
1958+
operations,
1959+
vec![
1960+
AlterTableOperation::AddColumn {
1961+
column_keyword: true,
1962+
if_not_exists: false,
1963+
column_def: ColumnDef {
1964+
name: "a".into(),
1965+
data_type: DataType::Text,
1966+
collation: None,
1967+
options: vec![],
1968+
},
1969+
column_position: Some(MySQLColumnPosition::First),
1970+
},
1971+
AlterTableOperation::AddColumn {
1972+
column_keyword: true,
1973+
if_not_exists: false,
1974+
column_def: ColumnDef {
1975+
name: "b".into(),
1976+
data_type: DataType::Int(None),
1977+
collation: None,
1978+
options: vec![],
1979+
},
1980+
column_position: Some(MySQLColumnPosition::After(Ident {
1981+
value: String::from("foo"),
1982+
quote_style: None,
1983+
})),
1984+
},
1985+
]
1986+
);
1987+
}
1988+
_ => unreachable!(),
1989+
}
1990+
}
1991+
18781992
#[test]
18791993
fn parse_alter_table_drop_primary_key() {
18801994
assert_matches!(
@@ -1891,6 +2005,7 @@ fn parse_alter_table_change_column() {
18912005
new_name: Ident::new("desc"),
18922006
data_type: DataType::Text,
18932007
options: vec![ColumnOption::NotNull],
2008+
column_position: None,
18942009
};
18952010

18962011
let sql1 = "ALTER TABLE orders CHANGE COLUMN description desc TEXT NOT NULL";
@@ -1904,6 +2019,80 @@ fn parse_alter_table_change_column() {
19042019
&expected_name.to_string(),
19052020
);
19062021
assert_eq!(expected_operation, operation);
2022+
2023+
let expected_operation = AlterTableOperation::ChangeColumn {
2024+
old_name: Ident::new("description"),
2025+
new_name: Ident::new("desc"),
2026+
data_type: DataType::Text,
2027+
options: vec![ColumnOption::NotNull],
2028+
column_position: Some(MySQLColumnPosition::First),
2029+
};
2030+
let sql3 = "ALTER TABLE orders CHANGE COLUMN description desc TEXT NOT NULL FIRST";
2031+
let operation =
2032+
alter_table_op_with_name(mysql().verified_stmt(sql3), &expected_name.to_string());
2033+
assert_eq!(expected_operation, operation);
2034+
2035+
let expected_operation = AlterTableOperation::ChangeColumn {
2036+
old_name: Ident::new("description"),
2037+
new_name: Ident::new("desc"),
2038+
data_type: DataType::Text,
2039+
options: vec![ColumnOption::NotNull],
2040+
column_position: Some(MySQLColumnPosition::After(Ident {
2041+
value: String::from("foo"),
2042+
quote_style: None,
2043+
})),
2044+
};
2045+
let sql4 = "ALTER TABLE orders CHANGE COLUMN description desc TEXT NOT NULL AFTER foo";
2046+
let operation =
2047+
alter_table_op_with_name(mysql().verified_stmt(sql4), &expected_name.to_string());
2048+
assert_eq!(expected_operation, operation);
2049+
}
2050+
2051+
#[test]
2052+
fn parse_alter_table_change_column_with_column_position() {
2053+
let expected_name = ObjectName(vec![Ident::new("orders")]);
2054+
let expected_operation_first = AlterTableOperation::ChangeColumn {
2055+
old_name: Ident::new("description"),
2056+
new_name: Ident::new("desc"),
2057+
data_type: DataType::Text,
2058+
options: vec![ColumnOption::NotNull],
2059+
column_position: Some(MySQLColumnPosition::First),
2060+
};
2061+
2062+
let sql1 = "ALTER TABLE orders CHANGE COLUMN description desc TEXT NOT NULL FIRST";
2063+
let operation =
2064+
alter_table_op_with_name(mysql().verified_stmt(sql1), &expected_name.to_string());
2065+
assert_eq!(expected_operation_first, operation);
2066+
2067+
let sql2 = "ALTER TABLE orders CHANGE description desc TEXT NOT NULL FIRST";
2068+
let operation = alter_table_op_with_name(
2069+
mysql().one_statement_parses_to(sql2, sql1),
2070+
&expected_name.to_string(),
2071+
);
2072+
assert_eq!(expected_operation_first, operation);
2073+
2074+
let expected_operation_after = AlterTableOperation::ChangeColumn {
2075+
old_name: Ident::new("description"),
2076+
new_name: Ident::new("desc"),
2077+
data_type: DataType::Text,
2078+
options: vec![ColumnOption::NotNull],
2079+
column_position: Some(MySQLColumnPosition::After(Ident {
2080+
value: String::from("total_count"),
2081+
quote_style: None,
2082+
})),
2083+
};
2084+
2085+
let sql1 = "ALTER TABLE orders CHANGE COLUMN description desc TEXT NOT NULL AFTER total_count";
2086+
let operation =
2087+
alter_table_op_with_name(mysql().verified_stmt(sql1), &expected_name.to_string());
2088+
assert_eq!(expected_operation_after, operation);
2089+
2090+
let sql2 = "ALTER TABLE orders CHANGE description desc TEXT NOT NULL AFTER total_count";
2091+
let operation = alter_table_op_with_name(
2092+
mysql().one_statement_parses_to(sql2, sql1),
2093+
&expected_name.to_string(),
2094+
);
2095+
assert_eq!(expected_operation_after, operation);
19072096
}
19082097

19092098
#[test]

tests/sqlparser_postgres.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,7 @@ fn parse_alter_table_add_columns() {
694694
collation: None,
695695
options: vec![],
696696
},
697+
column_position: None,
697698
},
698699
AlterTableOperation::AddColumn {
699700
column_keyword: true,
@@ -704,6 +705,7 @@ fn parse_alter_table_add_columns() {
704705
collation: None,
705706
options: vec![],
706707
},
708+
column_position: None,
707709
},
708710
]
709711
);

0 commit comments

Comments
 (0)