Skip to main content

vespertide_core/schema/
reference.rs

1use serde::{Deserialize, Serialize};
2
3/// The referential action taken on child rows when the referenced parent row changes.
4///
5/// Used in `ForeignKeyDef::on_delete` and `ForeignKeyDef::on_update` to control cascading
6/// behaviour. In JSON model files these are written in `snake_case`
7/// (e.g. `"on_delete": "cascade"`).
8///
9/// This enum is `#[non_exhaustive]`: new variants may be added in future releases.
10/// Downstream `match` expressions should include a wildcard arm.
11#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
12#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
13#[serde(rename_all = "snake_case")]
14#[non_exhaustive]
15pub enum ReferenceAction {
16    /// Automatically delete or update child rows when the parent row is deleted or updated (`CASCADE`).
17    Cascade,
18    /// Prevent the parent row from being deleted or updated if child rows exist (`RESTRICT`).
19    Restrict,
20    /// Set the foreign key column(s) in child rows to `NULL` when the parent changes (`SET NULL`).
21    /// The column must be nullable.
22    SetNull,
23    /// Set the foreign key column(s) in child rows to their column default when the parent changes (`SET DEFAULT`).
24    SetDefault,
25    /// Do nothing to child rows; the database defers enforcement or raises an error (`NO ACTION`).
26    NoAction,
27}
28
29impl ReferenceAction {
30    /// SQL keyword representation as written in `ALTER TABLE ... ADD
31    /// CONSTRAINT ... FOREIGN KEY ... ON DELETE <keyword>` etc. Used by
32    /// `vespertide-query` when emitting raw SQL (e.g. the F11
33    /// `NOT VALID` + `VALIDATE` PG path, which bypasses the sea-query
34    /// `ForeignKey` builder).
35    #[must_use]
36    pub fn to_sql_keyword(&self) -> &'static str {
37        match self {
38            Self::Cascade => "CASCADE",
39            Self::Restrict => "RESTRICT",
40            Self::SetNull => "SET NULL",
41            Self::SetDefault => "SET DEFAULT",
42            Self::NoAction => "NO ACTION",
43        }
44    }
45}
46
47#[cfg(test)]
48mod tests {
49    //! Coverage-closure tests for `ReferenceAction::to_sql_keyword`.
50    //! Targets `uncovered-detail.json` lines 40, 41, 42
51    //! (`SetNull` / `SetDefault` / `NoAction` match arms).
52    use super::*;
53    use rstest::rstest;
54
55    #[rstest]
56    #[case::cascade(ReferenceAction::Cascade, "CASCADE")]
57    #[case::restrict(ReferenceAction::Restrict, "RESTRICT")]
58    #[case::set_null(ReferenceAction::SetNull, "SET NULL")]
59    #[case::set_default(ReferenceAction::SetDefault, "SET DEFAULT")]
60    #[case::no_action(ReferenceAction::NoAction, "NO ACTION")]
61    fn to_sql_keyword_emits_expected_token(
62        #[case] action: ReferenceAction,
63        #[case] expected: &'static str,
64    ) {
65        // Each rstest case visits one match arm of to_sql_keyword. The
66        // SetNull/SetDefault/NoAction cases cover the previously-uncovered
67        // lines 40, 41, 42.
68        assert_eq!(action.to_sql_keyword(), expected);
69    }
70}