MySQL: Support index_name in FK constraints (#1871)

This commit is contained in:
Mohamed Abdeen 2025-06-06 08:03:59 +01:00 committed by GitHub
parent 5327f0ce13
commit de2cc7b502
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 31 additions and 7 deletions

View file

@ -1019,6 +1019,9 @@ pub enum TableConstraint {
/// }`). /// }`).
ForeignKey { ForeignKey {
name: Option<Ident>, name: Option<Ident>,
/// MySQL-specific field
/// <https://dev.mysql.com/doc/refman/8.4/en/create-table-foreign-keys.html>
index_name: Option<Ident>,
columns: Vec<Ident>, columns: Vec<Ident>,
foreign_table: ObjectName, foreign_table: ObjectName,
referred_columns: Vec<Ident>, referred_columns: Vec<Ident>,
@ -1129,6 +1132,7 @@ impl fmt::Display for TableConstraint {
} }
TableConstraint::ForeignKey { TableConstraint::ForeignKey {
name, name,
index_name,
columns, columns,
foreign_table, foreign_table,
referred_columns, referred_columns,
@ -1138,8 +1142,9 @@ impl fmt::Display for TableConstraint {
} => { } => {
write!( write!(
f, f,
"{}FOREIGN KEY ({}) REFERENCES {}", "{}FOREIGN KEY{} ({}) REFERENCES {}",
display_constraint_name(name), display_constraint_name(name),
display_option_spaced(index_name),
display_comma_separated(columns), display_comma_separated(columns),
foreign_table, foreign_table,
)?; )?;

View file

@ -671,6 +671,7 @@ impl Spanned for TableConstraint {
TableConstraint::ForeignKey { TableConstraint::ForeignKey {
name, name,
columns, columns,
index_name,
foreign_table, foreign_table,
referred_columns, referred_columns,
on_delete, on_delete,
@ -679,6 +680,7 @@ impl Spanned for TableConstraint {
} => union_spans( } => union_spans(
name.iter() name.iter()
.map(|i| i.span) .map(|i| i.span)
.chain(index_name.iter().map(|i| i.span))
.chain(columns.iter().map(|i| i.span)) .chain(columns.iter().map(|i| i.span))
.chain(core::iter::once(foreign_table.span())) .chain(core::iter::once(foreign_table.span()))
.chain(referred_columns.iter().map(|i| i.span)) .chain(referred_columns.iter().map(|i| i.span))

View file

@ -8061,7 +8061,7 @@ impl<'a> Parser<'a> {
let nulls_distinct = self.parse_optional_nulls_distinct()?; let nulls_distinct = self.parse_optional_nulls_distinct()?;
// optional index name // optional index name
let index_name = self.parse_optional_indent()?; let index_name = self.parse_optional_ident()?;
let index_type = self.parse_optional_using_then_index_type()?; let index_type = self.parse_optional_using_then_index_type()?;
let columns = self.parse_parenthesized_column_list(Mandatory, false)?; let columns = self.parse_parenthesized_column_list(Mandatory, false)?;
@ -8083,7 +8083,7 @@ impl<'a> Parser<'a> {
self.expect_keyword_is(Keyword::KEY)?; self.expect_keyword_is(Keyword::KEY)?;
// optional index name // optional index name
let index_name = self.parse_optional_indent()?; let index_name = self.parse_optional_ident()?;
let index_type = self.parse_optional_using_then_index_type()?; let index_type = self.parse_optional_using_then_index_type()?;
let columns = self.parse_parenthesized_column_list(Mandatory, false)?; let columns = self.parse_parenthesized_column_list(Mandatory, false)?;
@ -8100,6 +8100,7 @@ impl<'a> Parser<'a> {
} }
Token::Word(w) if w.keyword == Keyword::FOREIGN => { Token::Word(w) if w.keyword == Keyword::FOREIGN => {
self.expect_keyword_is(Keyword::KEY)?; self.expect_keyword_is(Keyword::KEY)?;
let index_name = self.parse_optional_ident()?;
let columns = self.parse_parenthesized_column_list(Mandatory, false)?; let columns = self.parse_parenthesized_column_list(Mandatory, false)?;
self.expect_keyword_is(Keyword::REFERENCES)?; self.expect_keyword_is(Keyword::REFERENCES)?;
let foreign_table = self.parse_object_name(false)?; let foreign_table = self.parse_object_name(false)?;
@ -8122,6 +8123,7 @@ impl<'a> Parser<'a> {
Ok(Some(TableConstraint::ForeignKey { Ok(Some(TableConstraint::ForeignKey {
name, name,
index_name,
columns, columns,
foreign_table, foreign_table,
referred_columns, referred_columns,
@ -8145,7 +8147,7 @@ impl<'a> Parser<'a> {
let name = match self.peek_token().token { let name = match self.peek_token().token {
Token::Word(word) if word.keyword == Keyword::USING => None, Token::Word(word) if word.keyword == Keyword::USING => None,
_ => self.parse_optional_indent()?, _ => self.parse_optional_ident()?,
}; };
let index_type = self.parse_optional_using_then_index_type()?; let index_type = self.parse_optional_using_then_index_type()?;
@ -8176,7 +8178,7 @@ impl<'a> Parser<'a> {
let index_type_display = self.parse_index_type_display(); let index_type_display = self.parse_index_type_display();
let opt_index_name = self.parse_optional_indent()?; let opt_index_name = self.parse_optional_ident()?;
let columns = self.parse_parenthesized_column_list(Mandatory, false)?; let columns = self.parse_parenthesized_column_list(Mandatory, false)?;
@ -8286,7 +8288,7 @@ impl<'a> Parser<'a> {
/// Parse `[ident]`, mostly `ident` is name, like: /// Parse `[ident]`, mostly `ident` is name, like:
/// `window_name`, `index_name`, ... /// `window_name`, `index_name`, ...
pub fn parse_optional_indent(&mut self) -> Result<Option<Ident>, ParserError> { pub fn parse_optional_ident(&mut self) -> Result<Option<Ident>, ParserError> {
self.maybe_parse(|parser| parser.parse_identifier()) self.maybe_parse(|parser| parser.parse_identifier())
} }
@ -15698,7 +15700,7 @@ impl<'a> Parser<'a> {
pub fn parse_window_spec(&mut self) -> Result<WindowSpec, ParserError> { pub fn parse_window_spec(&mut self) -> Result<WindowSpec, ParserError> {
let window_name = match self.peek_token().token { let window_name = match self.peek_token().token {
Token::Word(word) if word.keyword == Keyword::NoKeyword => { Token::Word(word) if word.keyword == Keyword::NoKeyword => {
self.parse_optional_indent()? self.parse_optional_ident()?
} }
_ => None, _ => None,
}; };

View file

@ -3791,6 +3791,7 @@ fn parse_create_table() {
vec![ vec![
TableConstraint::ForeignKey { TableConstraint::ForeignKey {
name: Some("fkey".into()), name: Some("fkey".into()),
index_name: None,
columns: vec!["lat".into()], columns: vec!["lat".into()],
foreign_table: ObjectName::from(vec!["othertable3".into()]), foreign_table: ObjectName::from(vec!["othertable3".into()]),
referred_columns: vec!["lat".into()], referred_columns: vec!["lat".into()],
@ -3800,6 +3801,7 @@ fn parse_create_table() {
}, },
TableConstraint::ForeignKey { TableConstraint::ForeignKey {
name: Some("fkey2".into()), name: Some("fkey2".into()),
index_name: None,
columns: vec!["lat".into()], columns: vec!["lat".into()],
foreign_table: ObjectName::from(vec!["othertable4".into()]), foreign_table: ObjectName::from(vec!["othertable4".into()]),
referred_columns: vec!["lat".into()], referred_columns: vec!["lat".into()],
@ -3809,6 +3811,7 @@ fn parse_create_table() {
}, },
TableConstraint::ForeignKey { TableConstraint::ForeignKey {
name: None, name: None,
index_name: None,
columns: vec!["lat".into()], columns: vec!["lat".into()],
foreign_table: ObjectName::from(vec!["othertable4".into()]), foreign_table: ObjectName::from(vec!["othertable4".into()]),
referred_columns: vec!["lat".into()], referred_columns: vec!["lat".into()],
@ -3818,6 +3821,7 @@ fn parse_create_table() {
}, },
TableConstraint::ForeignKey { TableConstraint::ForeignKey {
name: None, name: None,
index_name: None,
columns: vec!["lng".into()], columns: vec!["lng".into()],
foreign_table: ObjectName::from(vec!["othertable4".into()]), foreign_table: ObjectName::from(vec!["othertable4".into()]),
referred_columns: vec!["longitude".into()], referred_columns: vec!["longitude".into()],
@ -3914,6 +3918,7 @@ fn parse_create_table_with_constraint_characteristics() {
vec![ vec![
TableConstraint::ForeignKey { TableConstraint::ForeignKey {
name: Some("fkey".into()), name: Some("fkey".into()),
index_name: None,
columns: vec!["lat".into()], columns: vec!["lat".into()],
foreign_table: ObjectName::from(vec!["othertable3".into()]), foreign_table: ObjectName::from(vec!["othertable3".into()]),
referred_columns: vec!["lat".into()], referred_columns: vec!["lat".into()],
@ -3927,6 +3932,7 @@ fn parse_create_table_with_constraint_characteristics() {
}, },
TableConstraint::ForeignKey { TableConstraint::ForeignKey {
name: Some("fkey2".into()), name: Some("fkey2".into()),
index_name: None,
columns: vec!["lat".into()], columns: vec!["lat".into()],
foreign_table: ObjectName::from(vec!["othertable4".into()]), foreign_table: ObjectName::from(vec!["othertable4".into()]),
referred_columns: vec!["lat".into()], referred_columns: vec!["lat".into()],
@ -3940,6 +3946,7 @@ fn parse_create_table_with_constraint_characteristics() {
}, },
TableConstraint::ForeignKey { TableConstraint::ForeignKey {
name: None, name: None,
index_name: None,
columns: vec!["lat".into()], columns: vec!["lat".into()],
foreign_table: ObjectName::from(vec!["othertable4".into()]), foreign_table: ObjectName::from(vec!["othertable4".into()]),
referred_columns: vec!["lat".into()], referred_columns: vec!["lat".into()],
@ -3953,6 +3960,7 @@ fn parse_create_table_with_constraint_characteristics() {
}, },
TableConstraint::ForeignKey { TableConstraint::ForeignKey {
name: None, name: None,
index_name: None,
columns: vec!["lng".into()], columns: vec!["lng".into()],
foreign_table: ObjectName::from(vec!["othertable4".into()]), foreign_table: ObjectName::from(vec!["othertable4".into()]),
referred_columns: vec!["longitude".into()], referred_columns: vec!["longitude".into()],

View file

@ -3988,6 +3988,13 @@ fn parse_straight_join() {
.verified_stmt("SELECT a.*, b.* FROM table_a STRAIGHT_JOIN table_b AS b ON a.b_id = b.id"); .verified_stmt("SELECT a.*, b.* FROM table_a STRAIGHT_JOIN table_b AS b ON a.b_id = b.id");
} }
#[test]
fn mysql_foreign_key_with_index_name() {
mysql().verified_stmt(
"CREATE TABLE orders (customer_id INT, INDEX idx_customer (customer_id), CONSTRAINT fk_customer FOREIGN KEY idx_customer (customer_id) REFERENCES customers(id))",
);
}
#[test] #[test]
fn parse_drop_index() { fn parse_drop_index() {
let sql = "DROP INDEX idx_name ON table_name"; let sql = "DROP INDEX idx_name ON table_name";