Skip to main content

turso_orm/entity/
relation.rs

1//! Relations between entities, modeled by [`RelationDef`], [`Related`] and [`Linked`].
2//!
3//! A relation is a static description of how two tables join: the columns
4//! on each side, which side owns the foreign key, and the referential
5//! actions. The same definition serves three consumers — the `JOIN ... ON`
6//! condition of a select, the batch loaders, and the `FOREIGN KEY` clause
7//! that [`Schema`](super::Schema) emits — so it is kept as plain data rather
8//! than behaviour.
9//!
10//! `belongs_to` and `has_one` share [`RelationType::HasOne`]; what tells
11//! them apart is [`RelationDef::is_owner`], which records whether the
12//! declaring table holds the foreign key and therefore whether a foreign
13//! key constraint is generated for it.
14//!
15//! A many-to-many relation is two hops through a junction table:
16//! [`Related::via`] names the first hop and [`Related::to`] the second. A
17//! [`Linked`] chain generalises that to any number of hops, each one an
18//! ordinary [`RelationDef`], so that a query can follow a path of relations
19//! without the entities in between having to be loaded.
20//!
21//! Join conditions can be rendered against table aliases through
22//! [`RelationDef::join_condition_refs`], which is what makes self-referencing
23//! relations and chains that revisit a table unambiguous.
24
25use turso_sql::{Expr, ForeignKey, ForeignKeyAction, JoinType};
26
27use super::base_entity::EntityTrait;
28use super::column::ColumnTrait;
29use super::iden::{IdenStatic, Iterable};
30
31/// The cardinality of a relation.
32#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub enum RelationType {
34    /// At most one row of the target.
35    HasOne,
36    /// Any number of rows of the target.
37    HasMany,
38}
39
40/// A relation from one entity (`from`) to another (`to`).
41#[derive(Clone, Debug, PartialEq, Eq)]
42pub struct RelationDef {
43    /// The cardinality.
44    pub rel_type: RelationType,
45    /// The table of the entity that declares the relation.
46    pub from_tbl: &'static str,
47    /// The table being related to.
48    pub to_tbl: &'static str,
49    /// The join columns on `from_tbl`, paired by position with `to_col`.
50    pub from_col: Vec<&'static str>,
51    /// The join columns on `to_tbl`, paired by position with `from_col`.
52    pub to_col: Vec<&'static str>,
53    /// Whether `from_tbl` holds the foreign key, as in `belongs_to`.
54    pub is_owner: bool,
55    /// The `ON DELETE` action for the generated foreign key.
56    pub on_delete: Option<ForeignKeyAction>,
57    /// The `ON UPDATE` action for the generated foreign key.
58    pub on_update: Option<ForeignKeyAction>,
59    /// Whether to skip generating a foreign key for this relation.
60    pub skip_fk: bool,
61}
62
63impl RelationDef {
64    /// The reverse relation, from `to` back to `from`.
65    ///
66    /// Ownership flips with the direction so that the foreign key is still
67    /// attributed to the table that holds it.
68    #[must_use]
69    pub fn rev(self) -> Self {
70        Self {
71            rel_type: self.rel_type,
72            from_tbl: self.to_tbl,
73            to_tbl: self.from_tbl,
74            from_col: self.to_col,
75            to_col: self.from_col,
76            is_owner: !self.is_owner,
77            on_delete: self.on_delete,
78            on_update: self.on_update,
79            skip_fk: self.skip_fk,
80        }
81    }
82
83    /// Builds the join condition `from.a = to.a AND from.b = to.b`.
84    pub fn join_condition(&self) -> Expr {
85        self.join_condition_refs(self.from_tbl, self.to_tbl)
86    }
87
88    /// Builds the join condition with each side qualified by the given
89    /// reference — a table name or the alias it was joined under.
90    ///
91    /// Once a table is aliased, SQL requires every qualified column to use
92    /// the alias, so the select builder passes the reference it chose for
93    /// each side instead of the table names stored on the relation.
94    pub fn join_condition_refs(&self, from_ref: &str, to_ref: &str) -> Expr {
95        let mut cond: Option<Expr> = None;
96        for (f, t) in self.from_col.iter().zip(&self.to_col) {
97            let e = Expr::col((from_ref.to_owned(), *f)).eq(Expr::col((to_ref.to_owned(), *t)));
98            cond = Some(match cond {
99                Some(c) => c.and(e),
100                None => e,
101            });
102        }
103        // A relation without columns cannot restrict the join; `TRUE` keeps
104        // the rendered SQL valid rather than emitting an empty `ON`.
105        cond.unwrap_or_else(|| Expr::val(true))
106    }
107
108    /// The foreign key this relation implies, when the declaring table owns
109    /// it and generation was not skipped.
110    pub fn foreign_key(&self) -> Option<ForeignKey> {
111        if !self.is_owner || self.skip_fk {
112            return None;
113        }
114        let mut fk = ForeignKey::new(
115            self.from_col.iter().copied(),
116            self.to_tbl,
117            self.to_col.iter().copied(),
118        );
119        if let Some(a) = self.on_delete {
120            fk = fk.on_delete(a);
121        }
122        if let Some(a) = self.on_update {
123            fk = fk.on_update(a);
124        }
125        Some(fk)
126    }
127
128    /// The join type `find_also_related` uses: `LEFT`, so that rows without a
129    /// related row are still returned.
130    pub fn default_join(&self) -> JoinType {
131        JoinType::Left
132    }
133}
134
135/// The relations an entity declares, derived on the `Relation` enum.
136pub trait RelationTrait: Iterable + std::fmt::Debug {
137    /// The definition of this relation.
138    fn def(&self) -> RelationDef;
139}
140
141/// Declares that `Self` is related to `R`, directly or through a junction.
142///
143/// For a direct relation only [`to`](Self::to) is defined. For a
144/// many-to-many relation, [`via`](Self::via) is the hop from `Self` to the
145/// junction table and [`to`](Self::to) the hop from the junction to `R`;
146/// every consumer — `find_related`, the joins, `find_also_related` and the
147/// loaders — follows both hops when `via` is present.
148pub trait Related<R: EntityTrait>: EntityTrait {
149    /// The relation reaching `R`: from `Self` directly, or from the junction
150    /// table when [`via`](Self::via) is defined.
151    fn to() -> RelationDef;
152
153    /// The relation from `Self` to the junction table of a many-to-many
154    /// relation, or `None` for a direct relation.
155    fn via() -> Option<RelationDef> {
156        None
157    }
158}
159
160/// A path of relations from one entity to another, through any number of
161/// intermediate tables.
162///
163/// Each hop is a [`RelationDef`] whose `from_tbl` is the `to_tbl` of the
164/// previous one, starting at [`FromEntity`](Self::FromEntity) and ending at
165/// [`ToEntity`](Self::ToEntity). A chain is written by hand as a unit
166/// struct, which keeps multi-hop paths explicit and lets the same two
167/// entities be linked by several different paths:
168///
169/// ```ignore
170/// pub struct PostToTag;
171///
172/// impl Linked for PostToTag {
173///     type FromEntity = post::Entity;
174///     type ToEntity = tag::Entity;
175///
176///     fn link(&self) -> Vec<RelationDef> {
177///         vec![
178///             post_tag::Relation::Post.def().rev(),
179///             post_tag::Relation::Tag.def(),
180///         ]
181///     }
182/// }
183/// ```
184pub trait Linked {
185    /// The entity the chain starts from.
186    type FromEntity: EntityTrait;
187    /// The entity the chain ends at.
188    type ToEntity: EntityTrait;
189
190    /// The hops, in order from [`FromEntity`](Self::FromEntity) to
191    /// [`ToEntity`](Self::ToEntity).
192    fn link(&self) -> Vec<RelationDef>;
193}
194
195/// Looks up a column variant of `E` by its SQL name.
196///
197/// Relation definitions store column names as strings, so consumers that
198/// need to read a model attribute have to map them back to variants.
199pub(crate) fn column_of<E: EntityTrait>(name: &str) -> Option<E::Column> {
200    E::Column::iter().find(|c| c.as_str() == name)
201}
202
203/// A builder for a [`RelationDef`], returned by [`EntityTrait::belongs_to`],
204/// [`EntityTrait::has_one`] and [`EntityTrait::has_many`].
205#[derive(Debug)]
206pub struct RelationBuilder<E: EntityTrait, R: EntityTrait> {
207    /// The definition being assembled.
208    def: RelationDef,
209    /// Ties the builder to the two entity types without storing them.
210    _e: std::marker::PhantomData<(E, R)>,
211}
212
213impl<E: EntityTrait, R: EntityTrait> RelationBuilder<E, R> {
214    /// Starts a relation of the given cardinality and ownership between `E` and `R`.
215    pub(crate) fn new(rel_type: RelationType, is_owner: bool) -> Self {
216        Self {
217            def: RelationDef {
218                rel_type,
219                from_tbl: E::TABLE_NAME,
220                to_tbl: R::TABLE_NAME,
221                from_col: Vec::new(),
222                to_col: Vec::new(),
223                is_owner,
224                on_delete: None,
225                on_update: None,
226                skip_fk: false,
227            },
228            _e: std::marker::PhantomData,
229        }
230    }
231
232    /// Adds a join column on `E`; call once per column of a composite key.
233    #[must_use]
234    pub fn from<C: ColumnTrait>(mut self, column: C) -> Self {
235        self.def.from_col.push(column.as_str());
236        self
237    }
238
239    /// Adds a join column on `R`; call once per column of a composite key.
240    #[must_use]
241    pub fn to<C: ColumnTrait>(mut self, column: C) -> Self {
242        self.def.to_col.push(column.as_str());
243        self
244    }
245
246    /// Sets the `ON DELETE` action of the generated foreign key.
247    #[must_use]
248    pub fn on_delete(mut self, action: ForeignKeyAction) -> Self {
249        self.def.on_delete = Some(action);
250        self
251    }
252
253    /// Sets the `ON UPDATE` action of the generated foreign key.
254    #[must_use]
255    pub fn on_update(mut self, action: ForeignKeyAction) -> Self {
256        self.def.on_update = Some(action);
257        self
258    }
259
260    /// Disables foreign key generation for this relation.
261    #[must_use]
262    pub fn skip_fk(mut self) -> Self {
263        self.def.skip_fk = true;
264        self
265    }
266}
267
268impl<E: EntityTrait, R: EntityTrait> From<RelationBuilder<E, R>> for RelationDef {
269    fn from(b: RelationBuilder<E, R>) -> Self {
270        b.def
271    }
272}