Skip to main content

sea_orm/entity/
link.rs

1use crate::{EntityTrait, QuerySelect, RelationDef, Select, join_tbl_on_condition};
2use sea_query::{
3    Alias, CommonTableExpression, Condition, ConditionType, IntoIden, IntoTableRef, JoinType,
4    UnionType,
5};
6
7/// One hop in a multi-hop [`Linked`] chain. Alias for [`RelationDef`].
8pub type LinkDef = RelationDef;
9
10/// A multi-hop traversal between two entities: a chain of [`LinkDef`] hops
11/// from `FromEntity` to `ToEntity`.
12///
13/// Use this when a single [`Related`](crate::Related) edge can't express
14/// the path (for example, "user → post → comment → author"). Implement
15/// [`link`](Self::link) to return the ordered list of hops; SeaORM joins
16/// them when [`ModelTrait::find_linked`](crate::ModelTrait::find_linked) is
17/// called.
18pub trait Linked {
19    /// Entity at the start of the chain.
20    type FromEntity: EntityTrait;
21
22    /// Entity reached at the end of the chain.
23    type ToEntity: EntityTrait;
24
25    /// Ordered chain of hops from `FromEntity` to `ToEntity`.
26    fn link(&self) -> Vec<LinkDef>;
27
28    /// Build a [`Select<ToEntity>`] that follows the chain.
29    fn find_linked(&self) -> Select<Self::ToEntity> {
30        find_linked(self.link().into_iter().rev(), JoinType::InnerJoin)
31    }
32}
33
34pub(crate) fn find_linked<I, E>(links: I, join: JoinType) -> Select<E>
35where
36    I: Iterator<Item = LinkDef>,
37    E: EntityTrait,
38{
39    let mut select = Select::new();
40    for (i, mut rel) in links.enumerate() {
41        let from_tbl = format!("r{i}").into_iden();
42        let to_tbl = if i > 0 {
43            format!("r{}", i - 1).into_iden()
44        } else {
45            rel.to_tbl.sea_orm_table().clone()
46        };
47        let table_ref = rel.from_tbl;
48
49        let mut condition = match rel.condition_type {
50            ConditionType::All => Condition::all(),
51            ConditionType::Any => Condition::any(),
52        };
53        condition = condition.add(join_tbl_on_condition(
54            from_tbl.clone(),
55            to_tbl.clone(),
56            rel.from_col,
57            rel.to_col,
58        ));
59        if let Some(f) = rel.on_condition.take() {
60            condition = condition.add(f(from_tbl.clone(), to_tbl.clone()));
61        }
62
63        select.query().join_as(join, table_ref, from_tbl, condition);
64    }
65    select
66}
67
68pub(crate) fn find_linked_recursive<E>(
69    mut initial_query: Select<E>,
70    mut link: Vec<LinkDef>,
71) -> Select<E>
72where
73    E: EntityTrait,
74{
75    let cte_name = Alias::new("cte");
76
77    let Some(first) = link.first_mut() else {
78        return initial_query;
79    };
80    first.from_tbl = cte_name.clone().into_table_ref();
81    let mut recursive_query: Select<E> =
82        find_linked(link.into_iter().rev(), JoinType::InnerJoin).select_only();
83    initial_query.query.exprs_mut_for_each(|expr| {
84        recursive_query.query.expr(expr.clone());
85    });
86
87    let mut cte_query = initial_query.query.clone();
88    cte_query.union(UnionType::All, recursive_query.query);
89
90    let cte = CommonTableExpression::new()
91        .table_name(cte_name.clone())
92        .query(cte_query)
93        .to_owned();
94
95    let mut select = E::find().select_only();
96    initial_query.query.exprs_mut_for_each(|expr| {
97        select.query.expr(expr.clone());
98    });
99    select
100        .query
101        .from_clear()
102        .from_as(cte_name, E::default())
103        .with_cte(cte);
104    select
105}