arcature_data/query/relation.rs
1//! Eager loading of typed relations over SeaORM.
2//!
3//! [`Relation`] is a thin marker that names a related entity so [`Query`]
4//! can eager-load it without the caller spelling out the `Related` impl. The
5//! underlying load delegates to SeaORM's `find_also_related` — Arcature does
6//! not reimplement the relation engine.
7
8use std::marker::PhantomData;
9
10use arcature_db::sea_orm;
11
12use crate::error::DataError;
13use crate::query::Query;
14
15/// A typed reference to a related entity `R`, for eager loading.
16///
17/// Construct with [`of`] and pass to [`Query::with`]. The relation
18/// is resolved through SeaORM's `Related<R>` impl on the parent entity, so the
19/// caller does not name the relation enum variant — the type alone selects it.
20pub struct Relation<R>
21where
22 R: sea_orm::EntityTrait,
23{
24 // The related entity type is the relation selector. SeaORM's
25 // `find_also_related::<R>` resolves the join through `Related<R>` on the
26 // parent entity, so no relation-name string is needed. `PhantomData`
27 // holds the type without requiring `R: Default`.
28 _related: PhantomData<R>,
29}
30
31/// Reference the related entity `R` for eager loading. Generic over `R` so
32/// the caller can name it explicitly:
33///
34/// ```ignore
35/// use arcature_data::Relation;
36/// query.with(Relation::of::<user::Entity>())
37/// ```
38///
39/// `R` is resolved through SeaORM's `Related<R>` impl on the parent entity.
40#[must_use]
41pub fn of<R>() -> Relation<R>
42where
43 R: sea_orm::EntityTrait,
44{
45 Relation {
46 _related: PhantomData,
47 }
48}
49
50impl<'db, E> Query<'db, E>
51where
52 E: sea_orm::EntityTrait,
53{
54 /// Eager-load a related entity `R` alongside each row of `E`, returning
55 /// `(parent, Option<related>)` pairs. Delegates to SeaORM's
56 /// `find_also_related`.
57 ///
58 /// ```ignore
59 /// let rows = post::Entity::query(db)
60 /// .with(arcature_data::Relation::of::<user::Entity>())
61 /// .await?; // Vec<(post::Model, Option<user::Model>)>
62 /// ```
63 ///
64 /// `R` must be `Default` — SeaORM's generated `Entity` is a unit struct
65 /// that derives `Default`, so this is satisfied by every
66 /// `#[derive(DeriveEntityModel)]` entity without extra work.
67 ///
68 /// # Errors
69 ///
70 /// Returns [`DataError::Database`] if the underlying SeaORM query fails.
71 pub async fn with<R>(
72 self,
73 _relation: Relation<R>,
74 ) -> Result<Vec<(E::Model, Option<R::Model>)>, DataError>
75 where
76 R: sea_orm::EntityTrait + Default,
77 E: sea_orm::Related<R>,
78 {
79 self.select
80 .find_also_related(R::default())
81 .all(self.db.orm())
82 .await
83 .map_err(DataError::from)
84 }
85}