Add referential actions to TableConstraint foreign key (#306)

* Add referential actions to TableConstraint foreign key

* Remove copy/paste error

* Add referential actions to TableConstraint foreign key

* Add additional tests
This commit is contained in:
joshwd36 2021-08-25 17:03:10 +01:00 committed by GitHub
parent f56e57470e
commit a95c81fb13
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 99 additions and 12 deletions

View file

@ -136,12 +136,17 @@ pub enum TableConstraint {
is_primary: bool,
},
/// A referential integrity constraint (`[ CONSTRAINT <name> ] FOREIGN KEY (<columns>)
/// REFERENCES <foreign_table> (<referred_columns>)`)
/// REFERENCES <foreign_table> (<referred_columns>)
/// { [ON DELETE <referential_action>] [ON UPDATE <referential_action>] |
/// [ON UPDATE <referential_action>] [ON DELETE <referential_action>]
/// }`).
ForeignKey {
name: Option<Ident>,
columns: Vec<Ident>,
foreign_table: ObjectName,
referred_columns: Vec<Ident>,
on_delete: Option<ReferentialAction>,
on_update: Option<ReferentialAction>,
},
/// `[ CONSTRAINT <name> ] CHECK (<expr>)`
Check {
@ -169,14 +174,25 @@ impl fmt::Display for TableConstraint {
columns,
foreign_table,
referred_columns,
} => write!(
on_delete,
on_update,
} => {
write!(
f,
"{}FOREIGN KEY ({}) REFERENCES {}({})",
display_constraint_name(name),
display_comma_separated(columns),
foreign_table,
display_comma_separated(referred_columns)
),
display_comma_separated(referred_columns),
)?;
if let Some(action) = on_delete {
write!(f, " ON DELETE {}", action)?;
}
if let Some(action) = on_update {
write!(f, " ON UPDATE {}", action)?;
}
Ok(())
}
TableConstraint::Check { name, expr } => {
write!(f, "{}CHECK ({})", display_constraint_name(name), expr)
}

View file

@ -1741,11 +1741,26 @@ impl<'a> Parser<'a> {
self.expect_keyword(Keyword::REFERENCES)?;
let foreign_table = self.parse_object_name()?;
let referred_columns = self.parse_parenthesized_column_list(Mandatory)?;
let mut on_delete = None;
let mut on_update = None;
loop {
if on_delete.is_none() && self.parse_keywords(&[Keyword::ON, Keyword::DELETE]) {
on_delete = Some(self.parse_referential_action()?);
} else if on_update.is_none()
&& self.parse_keywords(&[Keyword::ON, Keyword::UPDATE])
{
on_update = Some(self.parse_referential_action()?);
} else {
break;
}
}
Ok(Some(TableConstraint::ForeignKey {
name,
columns,
foreign_table,
referred_columns,
on_delete,
on_update,
}))
}
Token::Word(w) if w.keyword == Keyword::CHECK => {

View file

@ -1162,7 +1162,11 @@ fn parse_create_table() {
lng DOUBLE,
constrained INT NULL CONSTRAINT pkey PRIMARY KEY NOT NULL UNIQUE CHECK (constrained > 0),
ref INT REFERENCES othertable (a, b),\
ref2 INT references othertable2 on delete cascade on update no action\
ref2 INT references othertable2 on delete cascade on update no action,\
constraint fkey foreign key (lat) references othertable3 (lat) on delete restrict,\
constraint fkey2 foreign key (lat) references othertable4(lat) on delete no action on update restrict, \
foreign key (lat) references othertable4(lat) on update set default on delete cascade, \
FOREIGN KEY (lng) REFERENCES othertable4 (longitude) ON UPDATE SET NULL
)";
let ast = one_statement_parses_to(
sql,
@ -1172,7 +1176,11 @@ fn parse_create_table() {
lng DOUBLE, \
constrained INT NULL CONSTRAINT pkey PRIMARY KEY NOT NULL UNIQUE CHECK (constrained > 0), \
ref INT REFERENCES othertable (a, b), \
ref2 INT REFERENCES othertable2 ON DELETE CASCADE ON UPDATE NO ACTION)",
ref2 INT REFERENCES othertable2 ON DELETE CASCADE ON UPDATE NO ACTION, \
CONSTRAINT fkey FOREIGN KEY (lat) REFERENCES othertable3(lat) ON DELETE RESTRICT, \
CONSTRAINT fkey2 FOREIGN KEY (lat) REFERENCES othertable4(lat) ON DELETE NO ACTION ON UPDATE RESTRICT, \
FOREIGN KEY (lat) REFERENCES othertable4(lat) ON DELETE CASCADE ON UPDATE SET DEFAULT, \
FOREIGN KEY (lng) REFERENCES othertable4(longitude) ON UPDATE SET NULL)",
);
match ast {
Statement::CreateTable {
@ -1271,7 +1279,43 @@ fn parse_create_table() {
}
]
);
assert!(constraints.is_empty());
assert_eq!(
constraints,
vec![
TableConstraint::ForeignKey {
name: Some("fkey".into()),
columns: vec!["lat".into()],
foreign_table: ObjectName(vec!["othertable3".into()]),
referred_columns: vec!["lat".into()],
on_delete: Some(ReferentialAction::Restrict),
on_update: None
},
TableConstraint::ForeignKey {
name: Some("fkey2".into()),
columns: vec!["lat".into()],
foreign_table: ObjectName(vec!["othertable4".into()]),
referred_columns: vec!["lat".into()],
on_delete: Some(ReferentialAction::NoAction),
on_update: Some(ReferentialAction::Restrict)
},
TableConstraint::ForeignKey {
name: None,
columns: vec!["lat".into()],
foreign_table: ObjectName(vec!["othertable4".into()]),
referred_columns: vec!["lat".into()],
on_delete: Some(ReferentialAction::Cascade),
on_update: Some(ReferentialAction::SetDefault)
},
TableConstraint::ForeignKey {
name: None,
columns: vec!["lng".into()],
foreign_table: ObjectName(vec!["othertable4".into()]),
referred_columns: vec!["longitude".into()],
on_delete: None,
on_update: Some(ReferentialAction::SetNull)
},
]
);
assert_eq!(with_options, vec![]);
}
_ => unreachable!(),
@ -1290,6 +1334,18 @@ fn parse_create_table() {
.contains("Expected constraint details after CONSTRAINT <name>"));
}
#[test]
fn parse_create_table_with_multiple_on_delete_in_constraint_fails() {
parse_sql_statements(
"\
create table X (\
y_id int, \
foreign key (y_id) references Y (id) on delete cascade on update cascade on delete no action\
)",
)
.expect_err("should have failed");
}
#[test]
fn parse_create_table_with_multiple_on_delete_fails() {
parse_sql_statements(