Skip to main content

sea_orm/entity/
relation.rs

1use crate::{
2    EntityTrait, Identity, IdentityOf, Iterable, QuerySelect, Select, join_tbl_on_condition,
3};
4use core::marker::PhantomData;
5use sea_query::{
6    Condition, ConditionType, DynIden, ForeignKeyCreateStatement, IntoIden, JoinType,
7    TableForeignKey, TableRef,
8};
9use std::{fmt::Debug, sync::Arc};
10
11/// Cardinality of a relation.
12#[derive(Clone, Debug, PartialEq, Eq, Hash)]
13pub enum RelationType {
14    /// A 1-1 relation (also used as half of a `belongs_to` / `has_one` pair).
15    HasOne,
16    /// A 1-N relation.
17    HasMany,
18}
19
20/// `ON DELETE` / `ON UPDATE` action attached to a foreign key. Re-exported
21/// from [`sea_query`] for convenience.
22pub type ForeignKeyAction = sea_query::ForeignKeyAction;
23
24/// Each variant of an entity's `Relation` enum implements this trait,
25/// providing the [`RelationDef`] that backs joins, foreign keys, and
26/// related-model lookups.
27///
28/// Usually generated by `#[derive(DeriveRelation)]` (or by `#[sea_orm::model]`
29/// in the 2.0 dense format).
30pub trait RelationTrait: Iterable + Debug + 'static {
31    /// The [`RelationDef`] describing this relation.
32    fn def(&self) -> RelationDef;
33
34    /// Name of the relation variant (e.g. `"Fruit"`), used for diagnostics.
35    fn name(&self) -> String {
36        format!("{self:?}")
37    }
38}
39
40/// `Self` is related to entity `R`, which lets queries join the two tables
41/// without a manual `ON` clause. For many-to-many relations, override
42/// [`via`](Self::via) to point at the junction table.
43pub trait Related<R>
44where
45    R: EntityTrait,
46{
47    /// The relation from `Self` to `R`.
48    fn to() -> RelationDef;
49
50    /// Junction table relation, if `Self <-> R` is many-to-many.
51    fn via() -> Option<RelationDef> {
52        None
53    }
54
55    /// Build a [`Select<R>`] pre-joined with `Self` via this relation.
56    fn find_related() -> Select<R> {
57        Select::<R>::new().join_join_rev(JoinType::InnerJoin, Self::to(), Self::via())
58    }
59}
60
61/// Self-referential many-to-many relation: `Self <-> Self` through a
62/// junction table (e.g. `user_follower`).
63pub trait RelatedSelfVia<R>
64where
65    R: EntityTrait,
66{
67    /// The relation back to `Self` (via the junction).
68    fn to() -> RelationDef;
69
70    /// The relation from `Self` to the junction table.
71    fn via() -> RelationDef;
72
73    /// Build a [`Select<R>`] pre-joined with `Self` via the junction table.
74    fn find_related() -> Select<R> {
75        Select::<R>::new().join_join_rev(JoinType::InnerJoin, Self::to(), Some(Self::via()))
76    }
77}
78
79/// Concrete description of a relation between two entities: which tables
80/// and columns participate, whether `Self` is the FK-owning side, and the
81/// optional foreign-key constraint metadata.
82///
83/// Build one with [`RelationBuilder`] (returned by
84/// [`EntityTrait::belongs_to`] / [`has_one`](EntityTrait::has_one) /
85/// [`has_many`](EntityTrait::has_many)), or destructure one returned by a
86/// [`RelationTrait::def`] impl when you need to customise it.
87#[derive(derive_more::Debug, Clone)]
88pub struct RelationDef {
89    /// Cardinality of the relation.
90    pub rel_type: RelationType,
91    /// Table at the FK-owning end of the relation.
92    pub from_tbl: TableRef,
93    /// Table being pointed to by the foreign key.
94    pub to_tbl: TableRef,
95    /// FK column(s) on [`from_tbl`](Self::from_tbl).
96    pub from_col: Identity,
97    /// Referenced column(s) on [`to_tbl`](Self::to_tbl) (usually the primary key).
98    pub to_col: Identity,
99    /// `true` if `Self` is the FK-owning side (i.e. holds `from_col`).
100    pub is_owner: bool,
101    /// Skip emitting a `FOREIGN KEY` constraint when this relation is used
102    /// to generate schema DDL.
103    pub skip_fk: bool,
104    /// `ON DELETE` action for the foreign key.
105    pub on_delete: Option<ForeignKeyAction>,
106    /// `ON UPDATE` action for the foreign key.
107    pub on_update: Option<ForeignKeyAction>,
108    /// Extra predicate to AND/OR into the join's `ON` clause; see
109    /// [`RelationDef::on_condition`].
110    #[debug("{}", on_condition.is_some())]
111    pub on_condition: Option<Arc<dyn Fn(DynIden, DynIden) -> Condition>>,
112    /// Name of the foreign-key constraint to emit in DDL.
113    pub fk_name: Option<String>,
114    /// How [`on_condition`](Self::on_condition) is combined with the
115    /// column equality predicate (`All` = AND, `Any` = OR).
116    pub condition_type: ConditionType,
117}
118
119/// Idiomatically generate the join condition.
120///
121/// This allows using a [`RelationDef`] directly anywhere [`sea_query`] expects
122/// an [`IntoCondition`](sea_query::IntoCondition).
123///
124/// ## Examples
125///
126/// ```
127/// use sea_orm::tests_cfg::{cake, fruit};
128/// use sea_orm::{entity::*, sea_query::*};
129///
130/// let query = Query::select()
131///     .from(fruit::Entity)
132///     .inner_join(cake::Entity, fruit::Relation::Cake.def())
133///     .to_owned();
134///
135/// assert_eq!(
136///     query.to_string(MysqlQueryBuilder),
137///     r#"SELECT  FROM `fruit` INNER JOIN `cake` ON `fruit`.`cake_id` = `cake`.`id`"#
138/// );
139/// assert_eq!(
140///     query.to_string(PostgresQueryBuilder),
141///     r#"SELECT  FROM "fruit" INNER JOIN "cake" ON "fruit"."cake_id" = "cake"."id""#
142/// );
143/// assert_eq!(
144///     query.to_string(SqliteQueryBuilder),
145///     r#"SELECT  FROM "fruit" INNER JOIN "cake" ON "fruit"."cake_id" = "cake"."id""#
146/// );
147/// ```
148impl From<RelationDef> for Condition {
149    fn from(mut rel: RelationDef) -> Condition {
150        // Use table alias (if any) to construct the join condition
151        let from_tbl = match rel.from_tbl.sea_orm_table_alias() {
152            Some(alias) => alias,
153            None => rel.from_tbl.sea_orm_table(),
154        };
155        let to_tbl = match rel.to_tbl.sea_orm_table_alias() {
156            Some(alias) => alias,
157            None => rel.to_tbl.sea_orm_table(),
158        };
159        let owner_keys = rel.from_col;
160        let foreign_keys = rel.to_col;
161
162        let mut condition = match rel.condition_type {
163            ConditionType::All => Condition::all(),
164            ConditionType::Any => Condition::any(),
165        };
166
167        condition = condition.add(join_tbl_on_condition(
168            from_tbl.clone(),
169            to_tbl.clone(),
170            owner_keys,
171            foreign_keys,
172        ));
173        if let Some(f) = rel.on_condition.take() {
174            condition = condition.add(f(from_tbl.clone(), to_tbl.clone()));
175        }
176
177        condition
178    }
179}
180
181/// Fluent builder for a [`RelationDef`]. Construct one via
182/// [`EntityTrait::belongs_to`] / [`has_one`](EntityTrait::has_one) /
183/// [`has_many`](EntityTrait::has_many).
184#[derive(derive_more::Debug)]
185pub struct RelationBuilder<E, R>
186where
187    E: EntityTrait,
188    R: EntityTrait,
189{
190    entities: PhantomData<(E, R)>,
191    rel_type: RelationType,
192    from_tbl: TableRef,
193    to_tbl: TableRef,
194    from_col: Option<Identity>,
195    to_col: Option<Identity>,
196    is_owner: bool,
197    skip_fk: bool,
198    on_delete: Option<ForeignKeyAction>,
199    on_update: Option<ForeignKeyAction>,
200    #[debug("{}", on_condition.is_some())]
201    on_condition: Option<Arc<dyn Fn(DynIden, DynIden) -> Condition>>,
202    fk_name: Option<String>,
203    condition_type: ConditionType,
204}
205
206impl RelationDef {
207    /// Swap the `from` and `to` ends of this relation. Useful when joining
208    /// from the FK-pointed-at side back to the FK-owning side.
209    pub fn rev(self) -> Self {
210        Self {
211            rel_type: self.rel_type,
212            from_tbl: self.to_tbl,
213            to_tbl: self.from_tbl,
214            from_col: self.to_col,
215            to_col: self.from_col,
216            is_owner: !self.is_owner,
217            skip_fk: self.skip_fk,
218            on_delete: self.on_delete,
219            on_update: self.on_update,
220            on_condition: self.on_condition,
221            fk_name: None,
222            condition_type: self.condition_type,
223        }
224    }
225
226    /// Express the relation from a table alias.
227    ///
228    /// This is a shorter and more discoverable equivalent to modifying `from_tbl` field by hand.
229    ///
230    /// # Examples
231    ///
232    /// Here's a short synthetic example.
233    /// In real life you'd use aliases when the table name comes up twice and you need to disambiguate,
234    /// e.g. <https://github.com/SeaQL/sea-orm/discussions/2133>.
235    ///
236    /// ```
237    /// use sea_orm::{
238    ///     DbBackend,
239    ///     entity::*,
240    ///     query::*,
241    ///     tests_cfg::{cake, cake_filling},
242    /// };
243    /// use sea_query::Alias;
244    ///
245    /// let cf = "cf";
246    ///
247    /// assert_eq!(
248    ///     cake::Entity::find()
249    ///         .join_as(
250    ///             JoinType::LeftJoin,
251    ///             cake_filling::Relation::Cake.def().rev(),
252    ///             cf.clone()
253    ///         )
254    ///         .join(
255    ///             JoinType::LeftJoin,
256    ///             cake_filling::Relation::Filling.def().from_alias(cf)
257    ///         )
258    ///         .build(DbBackend::MySql)
259    ///         .to_string(),
260    ///     [
261    ///         "SELECT `cake`.`id`, `cake`.`name` FROM `cake`",
262    ///         "LEFT JOIN `cake_filling` AS `cf` ON `cake`.`id` = `cf`.`cake_id`",
263    ///         "LEFT JOIN `filling` ON `cf`.`filling_id` = `filling`.`id`",
264    ///     ]
265    ///     .join(" ")
266    /// );
267    /// ```
268    pub fn from_alias<A>(mut self, alias: A) -> Self
269    where
270        A: IntoIden,
271    {
272        self.from_tbl = self.from_tbl.alias(alias);
273        self
274    }
275
276    /// Set custom join ON condition.
277    ///
278    /// This method takes a closure with two parameters
279    /// denoting the left-hand side and right-hand side table in the join expression.
280    ///
281    /// This replaces the current condition if it is already set.
282    ///
283    /// # Examples
284    ///
285    /// ```
286    /// use sea_orm::{entity::*, query::*, DbBackend, tests_cfg::{cake, cake_filling}};
287    /// use sea_query::{Expr, ExprTrait, IntoCondition};
288    ///
289    /// assert_eq!(
290    ///     cake::Entity::find()
291    ///         .join(
292    ///             JoinType::LeftJoin,
293    ///             cake_filling::Relation::Cake
294    ///                 .def()
295    ///                 .rev()
296    ///                 .on_condition(|_left, right| {
297    ///                     Expr::col((right, cake_filling::Column::CakeId))
298    ///                         .gt(10i32)
299    ///                         .into_condition()
300    ///                 })
301    ///         )
302    ///         .build(DbBackend::MySql)
303    ///         .to_string(),
304    ///     [
305    ///         "SELECT `cake`.`id`, `cake`.`name` FROM `cake`",
306    ///         "LEFT JOIN `cake_filling` ON `cake`.`id` = `cake_filling`.`cake_id` AND `cake_filling`.`cake_id` > 10",
307    ///     ]
308    ///     .join(" ")
309    /// );
310    /// ```
311    pub fn on_condition<F>(mut self, f: F) -> Self
312    where
313        F: Fn(DynIden, DynIden) -> Condition + 'static,
314    {
315        self.on_condition = Some(Arc::new(f));
316        self
317    }
318
319    /// Set the condition type of join on expression
320    ///
321    /// # Examples
322    ///
323    /// ```
324    /// use sea_orm::{entity::*, query::*, DbBackend, tests_cfg::{cake, cake_filling}};
325    /// use sea_query::{Expr, ExprTrait, IntoCondition, ConditionType};
326    ///
327    /// assert_eq!(
328    ///     cake::Entity::find()
329    ///         .join(
330    ///             JoinType::LeftJoin,
331    ///             cake_filling::Relation::Cake
332    ///                 .def()
333    ///                 .rev()
334    ///                 .condition_type(ConditionType::Any)
335    ///                 .on_condition(|_left, right| {
336    ///                     Expr::col((right, cake_filling::Column::CakeId))
337    ///                         .gt(10i32)
338    ///                         .into_condition()
339    ///                 })
340    ///         )
341    ///         .build(DbBackend::MySql)
342    ///         .to_string(),
343    ///     [
344    ///         "SELECT `cake`.`id`, `cake`.`name` FROM `cake`",
345    ///         "LEFT JOIN `cake_filling` ON `cake`.`id` = `cake_filling`.`cake_id` OR `cake_filling`.`cake_id` > 10",
346    ///     ]
347    ///     .join(" ")
348    /// );
349    /// ```
350    pub fn condition_type(mut self, condition_type: ConditionType) -> Self {
351        self.condition_type = condition_type;
352        self
353    }
354}
355
356impl<E, R> RelationBuilder<E, R>
357where
358    E: EntityTrait,
359    R: EntityTrait,
360{
361    pub(crate) fn new(rel_type: RelationType, from: E, to: R, is_owner: bool) -> Self {
362        Self {
363            entities: PhantomData,
364            rel_type,
365            from_tbl: from.table_ref(),
366            to_tbl: to.table_ref(),
367            from_col: None,
368            to_col: None,
369            is_owner,
370            skip_fk: false,
371            on_delete: None,
372            on_update: None,
373            on_condition: None,
374            fk_name: None,
375            condition_type: ConditionType::All,
376        }
377    }
378
379    pub(crate) fn from_rel(rel_type: RelationType, rel: RelationDef, is_owner: bool) -> Self {
380        Self {
381            entities: PhantomData,
382            rel_type,
383            from_tbl: rel.from_tbl,
384            to_tbl: rel.to_tbl,
385            from_col: Some(rel.from_col),
386            to_col: Some(rel.to_col),
387            is_owner,
388            skip_fk: false,
389            on_delete: None,
390            on_update: None,
391            on_condition: None,
392            fk_name: None,
393            condition_type: ConditionType::All,
394        }
395    }
396
397    /// Column(s) on the FK-owning side. Accepts a column or a tuple of
398    /// columns for composite keys.
399    pub fn from<T>(mut self, identifier: T) -> Self
400    where
401        T: IdentityOf<E>,
402    {
403        self.from_col = Some(identifier.identity_of());
404        self
405    }
406
407    /// Column(s) on the referenced side (usually the related entity's
408    /// primary key). Accepts a column or a tuple of columns for composite
409    /// keys.
410    pub fn to<T>(mut self, identifier: T) -> Self
411    where
412        T: IdentityOf<R>,
413    {
414        self.to_col = Some(identifier.identity_of());
415        self
416    }
417
418    /// Don't emit a `FOREIGN KEY` constraint when this relation is used to
419    /// generate schema DDL.
420    pub fn skip_fk(mut self) -> Self {
421        self.skip_fk = true;
422        self
423    }
424
425    /// Set the `ON DELETE` action for the foreign key (e.g. `Cascade`,
426    /// `SetNull`).
427    pub fn on_delete(mut self, action: ForeignKeyAction) -> Self {
428        self.on_delete = Some(action);
429        self
430    }
431
432    /// Set the `ON UPDATE` action for the foreign key.
433    pub fn on_update(mut self, action: ForeignKeyAction) -> Self {
434        self.on_update = Some(action);
435        self
436    }
437
438    /// Add an extra predicate to the join's `ON` clause. The closure receives
439    /// the left-hand-side and right-hand-side table idens.
440    pub fn on_condition<F>(mut self, f: F) -> Self
441    where
442        F: Fn(DynIden, DynIden) -> Condition + 'static,
443    {
444        self.on_condition = Some(Arc::new(f));
445        self
446    }
447
448    /// Override the name of the `FOREIGN KEY` constraint emitted in DDL.
449    pub fn fk_name(mut self, fk_name: &str) -> Self {
450        self.fk_name = Some(fk_name.to_owned());
451        self
452    }
453
454    /// Combine the column equality and [`on_condition`](Self::on_condition)
455    /// with `AND` (`ConditionType::All`, default) or `OR` (`ConditionType::Any`).
456    pub fn condition_type(mut self, condition_type: ConditionType) -> Self {
457        self.condition_type = condition_type;
458        self
459    }
460}
461
462impl<E, R> From<RelationBuilder<E, R>> for RelationDef
463where
464    E: EntityTrait,
465    R: EntityTrait,
466{
467    fn from(b: RelationBuilder<E, R>) -> Self {
468        RelationDef {
469            rel_type: b.rel_type,
470            from_tbl: b.from_tbl,
471            to_tbl: b.to_tbl,
472            from_col: b.from_col.expect("Reference column is not set"),
473            to_col: b.to_col.expect("Owner column is not set"),
474            is_owner: b.is_owner,
475            skip_fk: b.skip_fk,
476            on_delete: b.on_delete,
477            on_update: b.on_update,
478            on_condition: b.on_condition,
479            fk_name: b.fk_name,
480            condition_type: b.condition_type,
481        }
482    }
483}
484
485macro_rules! set_foreign_key_stmt {
486    ( $relation: ident, $foreign_key: ident ) => {
487        let from_cols: Vec<String> = $relation
488            .from_col
489            .into_iter()
490            .map(|col| {
491                let col_name = col.to_string();
492                $foreign_key.from_col(col);
493                col_name
494            })
495            .collect();
496        for col in $relation.to_col.into_iter() {
497            $foreign_key.to_col(col);
498        }
499        if let Some(action) = $relation.on_delete {
500            $foreign_key.on_delete(action);
501        }
502        if let Some(action) = $relation.on_update {
503            $foreign_key.on_update(action);
504        }
505        let name = if let Some(name) = $relation.fk_name {
506            name
507        } else {
508            let from_tbl = &$relation.from_tbl.sea_orm_table().clone();
509            format!("fk-{}-{}", from_tbl.to_string(), from_cols.join("-"))
510        };
511        $foreign_key.name(&name);
512    };
513}
514
515impl From<RelationDef> for ForeignKeyCreateStatement {
516    fn from(relation: RelationDef) -> Self {
517        let mut foreign_key_stmt = Self::new();
518        set_foreign_key_stmt!(relation, foreign_key_stmt);
519        foreign_key_stmt
520            .from_tbl(relation.from_tbl)
521            .to_tbl(relation.to_tbl)
522            .take()
523    }
524}
525
526/// Creates a column definition for example to update a table.
527/// ```
528/// use sea_query::{Alias, IntoIden, MysqlQueryBuilder, TableAlterStatement, IntoTableRef, ConditionType};
529/// use sea_orm::{EnumIter, Iden, Identity, PrimaryKeyTrait, RelationDef, RelationTrait, RelationType};
530///
531/// let relation = RelationDef {
532///     rel_type: RelationType::HasOne,
533///     from_tbl: "foo".into_table_ref(),
534///     to_tbl: "bar".into_table_ref(),
535///     from_col: Identity::Unary("bar_id".into_iden()),
536///     to_col: Identity::Unary("bar_id".into_iden()),
537///     is_owner: false,
538///     on_delete: None,
539///     on_update: None,
540///     on_condition: None,
541///     fk_name: Some("foo-bar".to_string()),
542///     skip_fk: false,
543///     condition_type: ConditionType::All,
544/// };
545///
546/// let mut alter_table = TableAlterStatement::new()
547///     .table("foo")
548///     .add_foreign_key(&mut relation.into()).take();
549/// assert_eq!(
550///     alter_table.to_string(MysqlQueryBuilder::default()),
551///     "ALTER TABLE `foo` ADD CONSTRAINT `foo-bar` FOREIGN KEY (`bar_id`) REFERENCES `bar` (`bar_id`)"
552/// );
553/// ```
554impl From<RelationDef> for TableForeignKey {
555    fn from(relation: RelationDef) -> Self {
556        let mut foreign_key = Self::new();
557        set_foreign_key_stmt!(relation, foreign_key);
558        foreign_key
559            .from_tbl(relation.from_tbl.sea_orm_table().clone())
560            .to_tbl(relation.to_tbl.sea_orm_table().clone())
561            .take()
562    }
563}
564
565impl std::hash::Hash for RelationDef {
566    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
567        self.rel_type.hash(state);
568        self.from_tbl.sea_orm_table().hash(state);
569        self.to_tbl.sea_orm_table().hash(state);
570        self.from_col.hash(state);
571        self.to_col.hash(state);
572        self.is_owner.hash(state);
573    }
574}
575
576impl PartialEq for RelationDef {
577    fn eq(&self, other: &Self) -> bool {
578        self.rel_type.eq(&other.rel_type)
579            && self.from_tbl.eq(&other.from_tbl)
580            && self.to_tbl.eq(&other.to_tbl)
581            && itertools::equal(self.from_col.iter(), other.from_col.iter())
582            && itertools::equal(self.to_col.iter(), other.to_col.iter())
583            && self.is_owner.eq(&other.is_owner)
584    }
585}
586
587impl Eq for RelationDef {}
588
589#[cfg(test)]
590mod tests {
591    use crate::{
592        RelationBuilder, RelationDef,
593        tests_cfg::{cake, fruit},
594    };
595
596    #[cfg(not(feature = "sync"))]
597    #[test]
598    fn assert_relation_traits() {
599        fn assert_send_sync<T: Send>() {}
600
601        assert_send_sync::<RelationDef>();
602        assert_send_sync::<RelationBuilder<cake::Entity, fruit::Entity>>();
603    }
604}