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}