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