Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 25 additions & 9 deletions src/ast/ddl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -169,14 +174,25 @@ impl fmt::Display for TableConstraint {
columns,
foreign_table,
referred_columns,
} => write!(
f,
"{}FOREIGN KEY ({}) REFERENCES {}({})",
display_constraint_name(name),
display_comma_separated(columns),
foreign_table,
display_comma_separated(referred_columns)
),
on_delete,
on_update,
} => {
write!(
f,
"{}FOREIGN KEY ({}) REFERENCES {}({})",
display_constraint_name(name),
display_comma_separated(columns),
foreign_table,
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)
}
Expand Down
15 changes: 15 additions & 0 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down
62 changes: 59 additions & 3 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 {
Expand Down Expand Up @@ -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!(),
Expand All @@ -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(
Expand Down