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
75 changes: 39 additions & 36 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,12 @@ const EOF_TOKEN: TokenWithSpan = TokenWithSpan {
},
};

struct ReferenceClauses {
match_kind: Option<ConstraintReferenceMatchKind>,
on_delete: Option<ReferentialAction>,
on_update: Option<ReferentialAction>,
}

/// Composite types declarations using angle brackets syntax can be arbitrary
/// nested such that the following declaration is possible:
/// `ARRAY<ARRAY<INT>>`
Expand Down Expand Up @@ -10182,24 +10188,11 @@ impl<'a> Parser<'a> {
// PostgreSQL allows omitting the column list and
// uses the primary key column of the foreign table by default
let referred_columns = self.parse_parenthesized_column_list(Optional, false)?;
let mut match_kind = None;
let mut on_delete = None;
let mut on_update = None;
loop {
if match_kind.is_none() && self.parse_keyword(Keyword::MATCH) {
match_kind = Some(self.parse_match_kind()?);
} else 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;
}
}
let ReferenceClauses {
match_kind,
on_delete,
on_update,
} = self.parse_reference_clauses()?;
let characteristics = self.parse_constraint_characteristics()?;

Ok(Some(
Expand Down Expand Up @@ -10462,6 +10455,29 @@ impl<'a> Parser<'a> {
Ok(clustered_by)
}

/// Parse optional `MATCH`, `ON DELETE`, and `ON UPDATE` reference clauses.
fn parse_reference_clauses(&mut self) -> Result<ReferenceClauses, ParserError> {
let mut match_kind = None;
let mut on_delete = None;
let mut on_update = None;
loop {
if match_kind.is_none() && self.parse_keyword(Keyword::MATCH) {
match_kind = Some(self.parse_match_kind()?);
} else 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(ReferenceClauses {
match_kind,
on_delete,
on_update,
})
}

/// Parse a referential action used in foreign key clauses.
///
/// Recognized forms: `RESTRICT`, `CASCADE`, `SET NULL`, `NO ACTION`, `SET DEFAULT`.
Expand Down Expand Up @@ -10659,24 +10675,11 @@ impl<'a> Parser<'a> {
self.expect_keyword_is(Keyword::REFERENCES)?;
let foreign_table = self.parse_object_name(false)?;
let referred_columns = self.parse_parenthesized_column_list(Optional, false)?;
let mut match_kind = None;
let mut on_delete = None;
let mut on_update = None;
loop {
if match_kind.is_none() && self.parse_keyword(Keyword::MATCH) {
match_kind = Some(self.parse_match_kind()?);
} else 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;
}
}
let ReferenceClauses {
match_kind,
on_delete,
on_update,
} = self.parse_reference_clauses()?;

let characteristics = self.parse_constraint_characteristics()?;

Expand Down
86 changes: 86 additions & 0 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22124,3 +22124,89 @@ fn parse_cache_table_options_without_flag() {
);
}
}

#[test]
fn parse_reference_clauses_in_any_order() {
let dialects = all_dialects();
for clauses in [
"MATCH FULL ON DELETE CASCADE ON UPDATE SET NULL",
"MATCH FULL ON UPDATE SET NULL ON DELETE CASCADE",
"ON DELETE CASCADE MATCH FULL ON UPDATE SET NULL",
"ON DELETE CASCADE ON UPDATE SET NULL MATCH FULL",
"ON UPDATE SET NULL MATCH FULL ON DELETE CASCADE",
"ON UPDATE SET NULL ON DELETE CASCADE MATCH FULL",
] {
for prefix in [
"a INT REFERENCES r (id)",
"a INT, FOREIGN KEY (a) REFERENCES r(id)",
] {
let sql = format!("CREATE TABLE t ({prefix} {clauses} DEFERRABLE INITIALLY DEFERRED)");
let canonical = format!(
"CREATE TABLE t ({prefix} MATCH FULL ON DELETE CASCADE ON UPDATE SET NULL DEFERRABLE INITIALLY DEFERRED)"
);
dialects.one_statement_parses_to(&sql, &canonical);
}
}
}

#[test]
fn parse_reference_clauses_leave_duplicates_unconsumed() {
for (clauses, keyword, match_kind, on_delete, on_update) in [
(
"MATCH FULL MATCH SIMPLE",
Keyword::MATCH,
Some(ConstraintReferenceMatchKind::Full),
None,
None,
),
(
"ON DELETE CASCADE ON DELETE SET NULL",
Keyword::ON,
None,
Some(ReferentialAction::Cascade),
None,
),
(
"ON UPDATE RESTRICT ON UPDATE NO ACTION",
Keyword::ON,
None,
None,
Some(ReferentialAction::Restrict),
),
] {
for table_level in [false, true] {
let prefix = if table_level {
"FOREIGN KEY (a) REFERENCES r(id)"
} else {
"REFERENCES r(id)"
};
let sql = format!("{prefix} {clauses}");
let mut parser = Parser::new(&GenericDialect {}).try_with_sql(&sql).unwrap();
let constraint = if table_level {
let Some(TableConstraint::ForeignKey(constraint)) =
parser.parse_optional_table_constraint().unwrap()
else {
panic!("{sql}");
};
constraint
} else {
let Some(ColumnOption::ForeignKey(constraint)) =
parser.parse_optional_column_option().unwrap()
else {
panic!("{sql}");
};
constraint
};
assert_eq!(
(
constraint.match_kind,
constraint.on_delete,
constraint.on_update
),
(match_kind, on_delete, on_update),
"{sql}"
);
assert!(parser.peek_keyword(keyword), "{sql}");
}
}
}
Loading