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