Skip to content

Commit 98f97d0

Browse files
authored
Add support for "on delete cascade" column option (apache#170)
Specifically, `FOREIGN KEY REFERENCES <foreign_table> (<referred_columns>)` can now be followed by `ON DELETE <referential_action>` and/or by `ON UPDATE <referential_action>`.
1 parent 789fcc8 commit 98f97d0

File tree

5 files changed

+131
-11
lines changed

5 files changed

+131
-11
lines changed

src/ast/ddl.rs

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -155,10 +155,15 @@ pub enum ColumnOption {
155155
is_primary: bool,
156156
},
157157
/// A referential integrity constraint (`[FOREIGN KEY REFERENCES
158-
/// <foreign_table> (<referred_columns>)`).
158+
/// <foreign_table> (<referred_columns>)
159+
/// { [ON DELETE <referential_action>] [ON UPDATE <referential_action>] |
160+
/// [ON UPDATE <referential_action>] [ON DELETE <referential_action>]
161+
/// }`).
159162
ForeignKey {
160163
foreign_table: ObjectName,
161164
referred_columns: Vec<Ident>,
165+
on_delete: Option<ReferentialAction>,
166+
on_update: Option<ReferentialAction>,
162167
},
163168
// `CHECK (<expr>)`
164169
Check(Expr),
@@ -177,12 +182,21 @@ impl fmt::Display for ColumnOption {
177182
ForeignKey {
178183
foreign_table,
179184
referred_columns,
180-
} => write!(
181-
f,
182-
"REFERENCES {} ({})",
183-
foreign_table,
184-
display_comma_separated(referred_columns)
185-
),
185+
on_delete,
186+
on_update,
187+
} => {
188+
write!(f, "REFERENCES {}", foreign_table)?;
189+
if !referred_columns.is_empty() {
190+
write!(f, " ({})", display_comma_separated(referred_columns))?;
191+
}
192+
if let Some(action) = on_delete {
193+
write!(f, " ON DELETE {}", action)?;
194+
}
195+
if let Some(action) = on_update {
196+
write!(f, " ON UPDATE {}", action)?;
197+
}
198+
Ok(())
199+
}
186200
Check(expr) => write!(f, "CHECK ({})", expr),
187201
}
188202
}
@@ -200,3 +214,28 @@ fn display_constraint_name<'a>(name: &'a Option<Ident>) -> impl fmt::Display + '
200214
}
201215
ConstraintName(name)
202216
}
217+
218+
/// `<referential_action> =
219+
/// { RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT }`
220+
///
221+
/// Used in foreign key constraints in `ON UPDATE` and `ON DELETE` options.
222+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
223+
pub enum ReferentialAction {
224+
Restrict,
225+
Cascade,
226+
SetNull,
227+
NoAction,
228+
SetDefault,
229+
}
230+
231+
impl fmt::Display for ReferentialAction {
232+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
233+
f.write_str(match self {
234+
ReferentialAction::Restrict => "RESTRICT",
235+
ReferentialAction::Cascade => "CASCADE",
236+
ReferentialAction::SetNull => "SET NULL",
237+
ReferentialAction::NoAction => "NO ACTION",
238+
ReferentialAction::SetDefault => "SET DEFAULT",
239+
})
240+
}
241+
}

src/ast/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ use std::fmt;
2222

2323
pub use self::data_type::DataType;
2424
pub use self::ddl::{
25-
AlterTableOperation, ColumnDef, ColumnOption, ColumnOptionDef, TableConstraint,
25+
AlterTableOperation, ColumnDef, ColumnOption, ColumnOptionDef, ReferentialAction,
26+
TableConstraint,
2627
};
2728
pub use self::operator::{BinaryOperator, UnaryOperator};
2829
pub use self::query::{

src/dialect/keywords.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ macro_rules! define_keywords {
5151

5252
define_keywords!(
5353
ABS,
54+
ACTION,
5455
ADD,
5556
ASC,
5657
ALL,

src/parser.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1038,10 +1038,25 @@ impl Parser {
10381038
ColumnOption::Unique { is_primary: false }
10391039
} else if self.parse_keyword("REFERENCES") {
10401040
let foreign_table = self.parse_object_name()?;
1041-
let referred_columns = self.parse_parenthesized_column_list(Mandatory)?;
1041+
// PostgreSQL allows omitting the column list and
1042+
// uses the primary key column of the foreign table by default
1043+
let referred_columns = self.parse_parenthesized_column_list(Optional)?;
1044+
let mut on_delete = None;
1045+
let mut on_update = None;
1046+
loop {
1047+
if on_delete.is_none() && self.parse_keywords(vec!["ON", "DELETE"]) {
1048+
on_delete = Some(self.parse_referential_action()?);
1049+
} else if on_update.is_none() && self.parse_keywords(vec!["ON", "UPDATE"]) {
1050+
on_update = Some(self.parse_referential_action()?);
1051+
} else {
1052+
break;
1053+
}
1054+
}
10421055
ColumnOption::ForeignKey {
10431056
foreign_table,
10441057
referred_columns,
1058+
on_delete,
1059+
on_update,
10451060
}
10461061
} else if self.parse_keyword("CHECK") {
10471062
self.expect_token(&Token::LParen)?;
@@ -1055,6 +1070,25 @@ impl Parser {
10551070
Ok(ColumnOptionDef { name, option })
10561071
}
10571072

1073+
pub fn parse_referential_action(&mut self) -> Result<ReferentialAction, ParserError> {
1074+
if self.parse_keyword("RESTRICT") {
1075+
Ok(ReferentialAction::Restrict)
1076+
} else if self.parse_keyword("CASCADE") {
1077+
Ok(ReferentialAction::Cascade)
1078+
} else if self.parse_keywords(vec!["SET", "NULL"]) {
1079+
Ok(ReferentialAction::SetNull)
1080+
} else if self.parse_keywords(vec!["NO", "ACTION"]) {
1081+
Ok(ReferentialAction::NoAction)
1082+
} else if self.parse_keywords(vec!["SET", "DEFAULT"]) {
1083+
Ok(ReferentialAction::SetDefault)
1084+
} else {
1085+
self.expected(
1086+
"one of RESTRICT, CASCADE, SET NULL, NO ACTION or SET DEFAULT",
1087+
self.peek_token(),
1088+
)
1089+
}
1090+
}
1091+
10581092
pub fn parse_optional_table_constraint(
10591093
&mut self,
10601094
) -> Result<Option<TableConstraint>, ParserError> {

tests/sqlparser_common.rs

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -893,15 +893,18 @@ fn parse_create_table() {
893893
lat DOUBLE NULL,\
894894
lng DOUBLE,
895895
constrained INT NULL CONSTRAINT pkey PRIMARY KEY NOT NULL UNIQUE CHECK (constrained > 0),
896-
ref INT REFERENCES othertable (a, b))";
896+
ref INT REFERENCES othertable (a, b),\
897+
ref2 INT references othertable2 on delete cascade on update no action\
898+
)";
897899
let ast = one_statement_parses_to(
898900
sql,
899901
"CREATE TABLE uk_cities (\
900902
name character varying(100) NOT NULL, \
901903
lat double NULL, \
902904
lng double, \
903905
constrained int NULL CONSTRAINT pkey PRIMARY KEY NOT NULL UNIQUE CHECK (constrained > 0), \
904-
ref int REFERENCES othertable (a, b))",
906+
ref int REFERENCES othertable (a, b), \
907+
ref2 int REFERENCES othertable2 ON DELETE CASCADE ON UPDATE NO ACTION)",
905908
);
906909
match ast {
907910
Statement::CreateTable {
@@ -978,8 +981,24 @@ fn parse_create_table() {
978981
option: ColumnOption::ForeignKey {
979982
foreign_table: ObjectName(vec!["othertable".into()]),
980983
referred_columns: vec!["a".into(), "b".into(),],
984+
on_delete: None,
985+
on_update: None,
981986
}
982987
}]
988+
},
989+
ColumnDef {
990+
name: "ref2".into(),
991+
data_type: DataType::Int,
992+
collation: None,
993+
options: vec![ColumnOptionDef {
994+
name: None,
995+
option: ColumnOption::ForeignKey {
996+
foreign_table: ObjectName(vec!["othertable2".into()]),
997+
referred_columns: vec![],
998+
on_delete: Some(ReferentialAction::Cascade),
999+
on_update: Some(ReferentialAction::NoAction),
1000+
}
1001+
},]
9831002
}
9841003
]
9851004
);
@@ -996,6 +1015,32 @@ fn parse_create_table() {
9961015
.contains("Expected column option, found: GARBAGE"));
9971016
}
9981017

1018+
#[test]
1019+
fn parse_create_table_with_multiple_on_delete_fails() {
1020+
parse_sql_statements(
1021+
"\
1022+
create table X (\
1023+
y_id int references Y (id) \
1024+
on delete cascade on update cascade on delete no action\
1025+
)",
1026+
)
1027+
.expect_err("should have failed");
1028+
}
1029+
1030+
#[test]
1031+
fn parse_create_table_with_on_delete_on_update_2in_any_order() -> Result<(), ParserError> {
1032+
let sql = |options: &str| -> String {
1033+
format!("create table X (y_id int references Y (id) {})", options)
1034+
};
1035+
1036+
parse_sql_statements(&sql("on update cascade on delete no action"))?;
1037+
parse_sql_statements(&sql("on delete cascade on update cascade"))?;
1038+
parse_sql_statements(&sql("on update no action"))?;
1039+
parse_sql_statements(&sql("on delete restrict"))?;
1040+
1041+
Ok(())
1042+
}
1043+
9991044
#[test]
10001045
fn parse_create_table_with_options() {
10011046
let sql = "CREATE TABLE t (c int) WITH (foo = 'bar', a = 123)";

0 commit comments

Comments
 (0)