Skip to content

Commit 09f3ffa

Browse files
Parser: Share foreign-key reference clause parsing
1 parent e997a09 commit 09f3ffa

2 files changed

Lines changed: 125 additions & 36 deletions

File tree

‎src/parser/mod.rs‎

Lines changed: 39 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,12 @@ const EOF_TOKEN: TokenWithSpan = TokenWithSpan {
204204
},
205205
};
206206

207+
struct ReferenceClauses {
208+
match_kind: Option<ConstraintReferenceMatchKind>,
209+
on_delete: Option<ReferentialAction>,
210+
on_update: Option<ReferentialAction>,
211+
}
212+
207213
/// Composite types declarations using angle brackets syntax can be arbitrary
208214
/// nested such that the following declaration is possible:
209215
/// `ARRAY<ARRAY<INT>>`
@@ -10182,24 +10188,11 @@ impl<'a> Parser<'a> {
1018210188
// PostgreSQL allows omitting the column list and
1018310189
// uses the primary key column of the foreign table by default
1018410190
let referred_columns = self.parse_parenthesized_column_list(Optional, false)?;
10185-
let mut match_kind = None;
10186-
let mut on_delete = None;
10187-
let mut on_update = None;
10188-
loop {
10189-
if match_kind.is_none() && self.parse_keyword(Keyword::MATCH) {
10190-
match_kind = Some(self.parse_match_kind()?);
10191-
} else if on_delete.is_none()
10192-
&& self.parse_keywords(&[Keyword::ON, Keyword::DELETE])
10193-
{
10194-
on_delete = Some(self.parse_referential_action()?);
10195-
} else if on_update.is_none()
10196-
&& self.parse_keywords(&[Keyword::ON, Keyword::UPDATE])
10197-
{
10198-
on_update = Some(self.parse_referential_action()?);
10199-
} else {
10200-
break;
10201-
}
10202-
}
10191+
let ReferenceClauses {
10192+
match_kind,
10193+
on_delete,
10194+
on_update,
10195+
} = self.parse_reference_clauses()?;
1020310196
let characteristics = self.parse_constraint_characteristics()?;
1020410197

1020510198
Ok(Some(
@@ -10462,6 +10455,29 @@ impl<'a> Parser<'a> {
1046210455
Ok(clustered_by)
1046310456
}
1046410457

10458+
/// Parse optional `MATCH`, `ON DELETE`, and `ON UPDATE` reference clauses.
10459+
fn parse_reference_clauses(&mut self) -> Result<ReferenceClauses, ParserError> {
10460+
let mut match_kind = None;
10461+
let mut on_delete = None;
10462+
let mut on_update = None;
10463+
loop {
10464+
if match_kind.is_none() && self.parse_keyword(Keyword::MATCH) {
10465+
match_kind = Some(self.parse_match_kind()?);
10466+
} else if on_delete.is_none() && self.parse_keywords(&[Keyword::ON, Keyword::DELETE]) {
10467+
on_delete = Some(self.parse_referential_action()?);
10468+
} else if on_update.is_none() && self.parse_keywords(&[Keyword::ON, Keyword::UPDATE]) {
10469+
on_update = Some(self.parse_referential_action()?);
10470+
} else {
10471+
break;
10472+
}
10473+
}
10474+
Ok(ReferenceClauses {
10475+
match_kind,
10476+
on_delete,
10477+
on_update,
10478+
})
10479+
}
10480+
1046510481
/// Parse a referential action used in foreign key clauses.
1046610482
///
1046710483
/// Recognized forms: `RESTRICT`, `CASCADE`, `SET NULL`, `NO ACTION`, `SET DEFAULT`.
@@ -10659,24 +10675,11 @@ impl<'a> Parser<'a> {
1065910675
self.expect_keyword_is(Keyword::REFERENCES)?;
1066010676
let foreign_table = self.parse_object_name(false)?;
1066110677
let referred_columns = self.parse_parenthesized_column_list(Optional, false)?;
10662-
let mut match_kind = None;
10663-
let mut on_delete = None;
10664-
let mut on_update = None;
10665-
loop {
10666-
if match_kind.is_none() && self.parse_keyword(Keyword::MATCH) {
10667-
match_kind = Some(self.parse_match_kind()?);
10668-
} else if on_delete.is_none()
10669-
&& self.parse_keywords(&[Keyword::ON, Keyword::DELETE])
10670-
{
10671-
on_delete = Some(self.parse_referential_action()?);
10672-
} else if on_update.is_none()
10673-
&& self.parse_keywords(&[Keyword::ON, Keyword::UPDATE])
10674-
{
10675-
on_update = Some(self.parse_referential_action()?);
10676-
} else {
10677-
break;
10678-
}
10679-
}
10678+
let ReferenceClauses {
10679+
match_kind,
10680+
on_delete,
10681+
on_update,
10682+
} = self.parse_reference_clauses()?;
1068010683

1068110684
let characteristics = self.parse_constraint_characteristics()?;
1068210685

‎tests/sqlparser_common.rs‎

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22124,3 +22124,89 @@ fn parse_cache_table_options_without_flag() {
2212422124
);
2212522125
}
2212622126
}
22127+
22128+
#[test]
22129+
fn parse_reference_clauses_in_any_order() {
22130+
let dialects = all_dialects();
22131+
for clauses in [
22132+
"MATCH FULL ON DELETE CASCADE ON UPDATE SET NULL",
22133+
"MATCH FULL ON UPDATE SET NULL ON DELETE CASCADE",
22134+
"ON DELETE CASCADE MATCH FULL ON UPDATE SET NULL",
22135+
"ON DELETE CASCADE ON UPDATE SET NULL MATCH FULL",
22136+
"ON UPDATE SET NULL MATCH FULL ON DELETE CASCADE",
22137+
"ON UPDATE SET NULL ON DELETE CASCADE MATCH FULL",
22138+
] {
22139+
for prefix in [
22140+
"a INT REFERENCES r (id)",
22141+
"a INT, FOREIGN KEY (a) REFERENCES r(id)",
22142+
] {
22143+
let sql = format!("CREATE TABLE t ({prefix} {clauses} DEFERRABLE INITIALLY DEFERRED)");
22144+
let canonical = format!(
22145+
"CREATE TABLE t ({prefix} MATCH FULL ON DELETE CASCADE ON UPDATE SET NULL DEFERRABLE INITIALLY DEFERRED)"
22146+
);
22147+
dialects.one_statement_parses_to(&sql, &canonical);
22148+
}
22149+
}
22150+
}
22151+
22152+
#[test]
22153+
fn parse_reference_clauses_leave_duplicates_unconsumed() {
22154+
for (clauses, keyword, match_kind, on_delete, on_update) in [
22155+
(
22156+
"MATCH FULL MATCH SIMPLE",
22157+
Keyword::MATCH,
22158+
Some(ConstraintReferenceMatchKind::Full),
22159+
None,
22160+
None,
22161+
),
22162+
(
22163+
"ON DELETE CASCADE ON DELETE SET NULL",
22164+
Keyword::ON,
22165+
None,
22166+
Some(ReferentialAction::Cascade),
22167+
None,
22168+
),
22169+
(
22170+
"ON UPDATE RESTRICT ON UPDATE NO ACTION",
22171+
Keyword::ON,
22172+
None,
22173+
None,
22174+
Some(ReferentialAction::Restrict),
22175+
),
22176+
] {
22177+
for table_level in [false, true] {
22178+
let prefix = if table_level {
22179+
"FOREIGN KEY (a) REFERENCES r(id)"
22180+
} else {
22181+
"REFERENCES r(id)"
22182+
};
22183+
let sql = format!("{prefix} {clauses}");
22184+
let mut parser = Parser::new(&GenericDialect {}).try_with_sql(&sql).unwrap();
22185+
let constraint = if table_level {
22186+
let Some(TableConstraint::ForeignKey(constraint)) =
22187+
parser.parse_optional_table_constraint().unwrap()
22188+
else {
22189+
panic!("{sql}");
22190+
};
22191+
constraint
22192+
} else {
22193+
let Some(ColumnOption::ForeignKey(constraint)) =
22194+
parser.parse_optional_column_option().unwrap()
22195+
else {
22196+
panic!("{sql}");
22197+
};
22198+
constraint
22199+
};
22200+
assert_eq!(
22201+
(
22202+
constraint.match_kind,
22203+
constraint.on_delete,
22204+
constraint.on_update
22205+
),
22206+
(match_kind, on_delete, on_update),
22207+
"{sql}"
22208+
);
22209+
assert!(parser.peek_keyword(keyword), "{sql}");
22210+
}
22211+
}
22212+
}

0 commit comments

Comments
 (0)