Skip to content

Commit a95c81f

Browse files
authored
Add referential actions to TableConstraint foreign key (apache#306)
* Add referential actions to TableConstraint foreign key * Remove copy/paste error * Add referential actions to TableConstraint foreign key * Add additional tests
1 parent f56e574 commit a95c81f

File tree

3 files changed

+99
-12
lines changed

3 files changed

+99
-12
lines changed

src/ast/ddl.rs

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -136,12 +136,17 @@ pub enum TableConstraint {
136136
is_primary: bool,
137137
},
138138
/// A referential integrity constraint (`[ CONSTRAINT <name> ] FOREIGN KEY (<columns>)
139-
/// REFERENCES <foreign_table> (<referred_columns>)`)
139+
/// REFERENCES <foreign_table> (<referred_columns>)
140+
/// { [ON DELETE <referential_action>] [ON UPDATE <referential_action>] |
141+
/// [ON UPDATE <referential_action>] [ON DELETE <referential_action>]
142+
/// }`).
140143
ForeignKey {
141144
name: Option<Ident>,
142145
columns: Vec<Ident>,
143146
foreign_table: ObjectName,
144147
referred_columns: Vec<Ident>,
148+
on_delete: Option<ReferentialAction>,
149+
on_update: Option<ReferentialAction>,
145150
},
146151
/// `[ CONSTRAINT <name> ] CHECK (<expr>)`
147152
Check {
@@ -169,14 +174,25 @@ impl fmt::Display for TableConstraint {
169174
columns,
170175
foreign_table,
171176
referred_columns,
172-
} => write!(
173-
f,
174-
"{}FOREIGN KEY ({}) REFERENCES {}({})",
175-
display_constraint_name(name),
176-
display_comma_separated(columns),
177-
foreign_table,
178-
display_comma_separated(referred_columns)
179-
),
177+
on_delete,
178+
on_update,
179+
} => {
180+
write!(
181+
f,
182+
"{}FOREIGN KEY ({}) REFERENCES {}({})",
183+
display_constraint_name(name),
184+
display_comma_separated(columns),
185+
foreign_table,
186+
display_comma_separated(referred_columns),
187+
)?;
188+
if let Some(action) = on_delete {
189+
write!(f, " ON DELETE {}", action)?;
190+
}
191+
if let Some(action) = on_update {
192+
write!(f, " ON UPDATE {}", action)?;
193+
}
194+
Ok(())
195+
}
180196
TableConstraint::Check { name, expr } => {
181197
write!(f, "{}CHECK ({})", display_constraint_name(name), expr)
182198
}

src/parser.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1741,11 +1741,26 @@ impl<'a> Parser<'a> {
17411741
self.expect_keyword(Keyword::REFERENCES)?;
17421742
let foreign_table = self.parse_object_name()?;
17431743
let referred_columns = self.parse_parenthesized_column_list(Mandatory)?;
1744+
let mut on_delete = None;
1745+
let mut on_update = None;
1746+
loop {
1747+
if on_delete.is_none() && self.parse_keywords(&[Keyword::ON, Keyword::DELETE]) {
1748+
on_delete = Some(self.parse_referential_action()?);
1749+
} else if on_update.is_none()
1750+
&& self.parse_keywords(&[Keyword::ON, Keyword::UPDATE])
1751+
{
1752+
on_update = Some(self.parse_referential_action()?);
1753+
} else {
1754+
break;
1755+
}
1756+
}
17441757
Ok(Some(TableConstraint::ForeignKey {
17451758
name,
17461759
columns,
17471760
foreign_table,
17481761
referred_columns,
1762+
on_delete,
1763+
on_update,
17491764
}))
17501765
}
17511766
Token::Word(w) if w.keyword == Keyword::CHECK => {

tests/sqlparser_common.rs

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1162,7 +1162,11 @@ fn parse_create_table() {
11621162
lng DOUBLE,
11631163
constrained INT NULL CONSTRAINT pkey PRIMARY KEY NOT NULL UNIQUE CHECK (constrained > 0),
11641164
ref INT REFERENCES othertable (a, b),\
1165-
ref2 INT references othertable2 on delete cascade on update no action\
1165+
ref2 INT references othertable2 on delete cascade on update no action,\
1166+
constraint fkey foreign key (lat) references othertable3 (lat) on delete restrict,\
1167+
constraint fkey2 foreign key (lat) references othertable4(lat) on delete no action on update restrict, \
1168+
foreign key (lat) references othertable4(lat) on update set default on delete cascade, \
1169+
FOREIGN KEY (lng) REFERENCES othertable4 (longitude) ON UPDATE SET NULL
11661170
)";
11671171
let ast = one_statement_parses_to(
11681172
sql,
@@ -1172,7 +1176,11 @@ fn parse_create_table() {
11721176
lng DOUBLE, \
11731177
constrained INT NULL CONSTRAINT pkey PRIMARY KEY NOT NULL UNIQUE CHECK (constrained > 0), \
11741178
ref INT REFERENCES othertable (a, b), \
1175-
ref2 INT REFERENCES othertable2 ON DELETE CASCADE ON UPDATE NO ACTION)",
1179+
ref2 INT REFERENCES othertable2 ON DELETE CASCADE ON UPDATE NO ACTION, \
1180+
CONSTRAINT fkey FOREIGN KEY (lat) REFERENCES othertable3(lat) ON DELETE RESTRICT, \
1181+
CONSTRAINT fkey2 FOREIGN KEY (lat) REFERENCES othertable4(lat) ON DELETE NO ACTION ON UPDATE RESTRICT, \
1182+
FOREIGN KEY (lat) REFERENCES othertable4(lat) ON DELETE CASCADE ON UPDATE SET DEFAULT, \
1183+
FOREIGN KEY (lng) REFERENCES othertable4(longitude) ON UPDATE SET NULL)",
11761184
);
11771185
match ast {
11781186
Statement::CreateTable {
@@ -1271,7 +1279,43 @@ fn parse_create_table() {
12711279
}
12721280
]
12731281
);
1274-
assert!(constraints.is_empty());
1282+
assert_eq!(
1283+
constraints,
1284+
vec![
1285+
TableConstraint::ForeignKey {
1286+
name: Some("fkey".into()),
1287+
columns: vec!["lat".into()],
1288+
foreign_table: ObjectName(vec!["othertable3".into()]),
1289+
referred_columns: vec!["lat".into()],
1290+
on_delete: Some(ReferentialAction::Restrict),
1291+
on_update: None
1292+
},
1293+
TableConstraint::ForeignKey {
1294+
name: Some("fkey2".into()),
1295+
columns: vec!["lat".into()],
1296+
foreign_table: ObjectName(vec!["othertable4".into()]),
1297+
referred_columns: vec!["lat".into()],
1298+
on_delete: Some(ReferentialAction::NoAction),
1299+
on_update: Some(ReferentialAction::Restrict)
1300+
},
1301+
TableConstraint::ForeignKey {
1302+
name: None,
1303+
columns: vec!["lat".into()],
1304+
foreign_table: ObjectName(vec!["othertable4".into()]),
1305+
referred_columns: vec!["lat".into()],
1306+
on_delete: Some(ReferentialAction::Cascade),
1307+
on_update: Some(ReferentialAction::SetDefault)
1308+
},
1309+
TableConstraint::ForeignKey {
1310+
name: None,
1311+
columns: vec!["lng".into()],
1312+
foreign_table: ObjectName(vec!["othertable4".into()]),
1313+
referred_columns: vec!["longitude".into()],
1314+
on_delete: None,
1315+
on_update: Some(ReferentialAction::SetNull)
1316+
},
1317+
]
1318+
);
12751319
assert_eq!(with_options, vec![]);
12761320
}
12771321
_ => unreachable!(),
@@ -1290,6 +1334,18 @@ fn parse_create_table() {
12901334
.contains("Expected constraint details after CONSTRAINT <name>"));
12911335
}
12921336

1337+
#[test]
1338+
fn parse_create_table_with_multiple_on_delete_in_constraint_fails() {
1339+
parse_sql_statements(
1340+
"\
1341+
create table X (\
1342+
y_id int, \
1343+
foreign key (y_id) references Y (id) on delete cascade on update cascade on delete no action\
1344+
)",
1345+
)
1346+
.expect_err("should have failed");
1347+
}
1348+
12931349
#[test]
12941350
fn parse_create_table_with_multiple_on_delete_fails() {
12951351
parse_sql_statements(

0 commit comments

Comments
 (0)