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