vespertide_core/schema/foreign_key.rs
1use serde::{Deserialize, Serialize};
2
3use crate::schema::{
4 fk_orphan_strategy::ForeignKeyOrphanStrategy, names::ColumnName, names::TableName,
5 reference::ReferenceAction,
6};
7
8/// `serde(skip_serializing_if)` helper - true when `orphan_strategy` is
9/// the canonical default. Mirror of the matching helper in
10/// `schema::constraint`.
11#[expect(
12 clippy::trivially_copy_pass_by_ref,
13 reason = "serde `skip_serializing_if` callbacks must have signature `fn(&T) -> bool`"
14)]
15fn is_default_fk_orphan_strategy(s: &ForeignKeyOrphanStrategy) -> bool {
16 matches!(s, ForeignKeyOrphanStrategy::NullifyOrphans)
17}
18
19/// Full foreign key definition used in the normalized table representation.
20///
21/// Specifies the referenced table and columns along with optional referential actions for
22/// `ON DELETE` and `ON UPDATE`. This is the canonical form produced after parsing any of the
23/// three [`ForeignKeySyntax`] variants.
24///
25/// Always add `"index": true` on the column carrying the foreign key for query performance.
26#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
27#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
28#[serde(rename_all = "snake_case")]
29pub struct ForeignKeyDef {
30 /// The table being referenced (the "parent" side of the relationship).
31 pub ref_table: TableName,
32 /// The column(s) in the referenced table that this foreign key points to.
33 pub ref_columns: Vec<ColumnName>,
34 /// Action to take on child rows when the parent row is deleted.
35 pub on_delete: Option<ReferenceAction>,
36 /// Action to take on child rows when the referenced column(s) in the parent row are updated.
37 pub on_update: Option<ReferenceAction>,
38 /// Pre-cleanup strategy for orphan child rows when the FK is added to
39 /// a populated table. See [`ForeignKeyOrphanStrategy`] for semantics;
40 /// the canonical default ([`ForeignKeyOrphanStrategy::NullifyOrphans`])
41 /// is omitted from the JSON wire format.
42 ///
43 /// **Stripped from `model.schema.json`** by the schema generator but
44 /// **preserved in `migration.schema.json`**.
45 #[serde(default, skip_serializing_if = "is_default_fk_orphan_strategy")]
46 pub orphan_strategy: ForeignKeyOrphanStrategy,
47}
48
49/// Compact foreign key syntax using a `"references"` string in `"table.column"` format.
50///
51/// Useful when you only need to specify the target and optionally an `on_delete` action without
52/// listing columns explicitly. The planner resolves the column from the `"table.column"` string.
53#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
54#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
55#[serde(rename_all = "snake_case")]
56pub struct ReferenceSyntaxDef {
57 /// Reference target in `"table.column"` format, e.g. `"user.id"`.
58 pub references: String,
59 #[serde(skip_serializing_if = "Option::is_none")]
60 pub on_delete: Option<ReferenceAction>,
61 #[serde(skip_serializing_if = "Option::is_none")]
62 pub on_update: Option<ReferenceAction>,
63}
64
65/// Inline foreign key declaration on a [`ColumnDef`], supporting three levels of verbosity.
66///
67/// In JSON model files you can write:
68/// - `"foreign_key": "user.id"` (string shorthand)
69/// - `"foreign_key": {"references": "user.id", "on_delete": "cascade"}` (reference object)
70/// - `"foreign_key": {"ref_table": "user", "ref_columns": ["id"], "on_delete": "cascade"}` (full object)
71///
72/// All three forms are normalized into a [`ForeignKeyDef`] by the planner.
73///
74/// [`ColumnDef`]: crate::schema::ColumnDef
75#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
76#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
77#[serde(rename_all = "snake_case", untagged)]
78pub enum ForeignKeySyntax {
79 /// Shorthand string in `"table.column"` format, e.g. `"user.id"`.
80 String(String),
81 /// Object with a `"references"` key in `"table.column"` format plus optional actions.
82 Reference(ReferenceSyntaxDef),
83 /// Full object with explicit `ref_table`, `ref_columns`, and optional actions.
84 Object(ForeignKeyDef),
85}