Skip to main content

sz_orm_core/
relation_trait.rs

1//! RelationTrait — 类型安全的关联关系定义与 JOIN 链式 API
2//!
3//! 提供 `RelationKind` / `RelationDef` / `RelationTrait` 核心类型,
4//! 配合 `#[derive(Relation)]` 宏自动生成 `RelationTrait` 实现,
5//! 追平 SeaORM `User::find().join(Posts)` 链式关联查询体验。
6//!
7//! # 设计
8//!
9//! - `RelationDef` 使用 `&'static str` 零分配描述关联关系
10//! - `RelationTrait` 提供 `def()` / `all_relations()` 方法
11//! - `RelationKind::default_join_type()` 决定 JOIN 类型(HasOne/BelongsTo → INNER,HasMany/ManyToMany → LEFT)
12//!
13//! # 用法
14//!
15//! ```ignore
16//! use sz_orm_core::relation_trait::{RelationDef, RelationKind, RelationTrait};
17//!
18//! struct User;
19//!
20//! impl RelationTrait for User {
21//!     fn def(&self) -> &'static RelationDef { &RELATIONS[0] }
22//!     fn all_relations() -> &'static [RelationDef] { RELATIONS }
23//! }
24//!
25//! static RELATIONS: &[RelationDef] = &[
26//!     RelationDef::new("orders", "users", "orders", "id", "user_id", RelationKind::HasMany),
27//! ];
28//! ```
29
30/// 关联关系类型
31///
32/// 决定 JOIN 策略和数据加载方式:
33/// - `HasOne` / `BelongsTo` → INNER JOIN(一条关联记录)
34/// - `HasMany` / `ManyToMany` → LEFT JOIN(多条关联记录,双查询策略避免行膨胀)
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
36pub enum RelationKind {
37    /// 一对一:当前实体拥有一个关联实体(如 User → Profile)
38    HasOne,
39    /// 一对多:当前实体拥有多个关联实体(如 User → Orders)
40    HasMany,
41    /// 多对一:当前实体属于一个父实体(如 Order → User)
42    BelongsTo,
43    /// 多对多:通过中间表关联(如 User ↔ Role,通过 user_roles)
44    ManyToMany,
45}
46
47impl RelationKind {
48    /// 返回该关系类型默认的 JOIN 类型
49    ///
50    /// - `HasOne` / `BelongsTo` → `JoinKind::Inner`(关联记录存在性要求)
51    /// - `HasMany` / `ManyToMany` → `JoinKind::Left`(允许零关联记录)
52    pub fn default_join_type(self) -> JoinKind {
53        match self {
54            RelationKind::HasOne | RelationKind::BelongsTo => JoinKind::Inner,
55            RelationKind::HasMany | RelationKind::ManyToMany => JoinKind::Left,
56        }
57    }
58}
59
60/// JOIN 类型(与 `join_dsl::JoinKind` 对齐,独立定义避免循环依赖)
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub enum JoinKind {
63    /// INNER JOIN
64    Inner,
65    /// LEFT \[OUTER\] JOIN
66    Left,
67}
68
69impl JoinKind {
70    /// 转换为 SQL 关键字
71    pub fn as_sql(self) -> &'static str {
72        match self {
73            JoinKind::Inner => "INNER JOIN",
74            JoinKind::Left => "LEFT JOIN",
75        }
76    }
77}
78
79/// 关联关系定义(零分配,编译期常量)
80///
81/// 描述两个实体间的关联关系,包含外键映射信息。
82/// 所有字段为 `&'static str`,运行时零分配。
83///
84/// v2.3.0 新增 ManyToMany 中间表元数据字段(`join_table`/`join_from_key`/`join_to_key`),
85/// 通过 [`RelationDef::new_many_to_many`] 构造器设置,`new()` 构造器保持向后兼容(中间表字段为 `None`)。
86#[derive(Debug, Clone)]
87pub struct RelationDef {
88    /// 关联名称(如 "orders"、"profile")
89    pub name: &'static str,
90    /// 源实体表名(如 "users")
91    pub from_entity: &'static str,
92    /// 目标实体表名(如 "orders")
93    pub to_entity: &'static str,
94    /// 源实体键列名(通常为主键,如 "id")
95    pub from_key: &'static str,
96    /// 目标实体外键列名(如 "user_id")
97    pub to_key: &'static str,
98    /// 关联类型
99    pub kind: RelationKind,
100    /// ManyToMany 中间表名(v2.3.0 新增,仅 `ManyToMany` 关联使用)
101    pub join_table: Option<&'static str>,
102    /// 中间表中指向源实体的外键列名(v2.3.0 新增)
103    pub join_from_key: Option<&'static str>,
104    /// 中间表中指向目标实体的外键列名(v2.3.0 新增)
105    pub join_to_key: Option<&'static str>,
106}
107
108impl RelationDef {
109    /// 创建关联关系定义
110    ///
111    /// v2.3.0 中间表字段默认为 `None`,确保 v2.2.0 代码零修改编译通过。
112    pub const fn new(
113        name: &'static str,
114        from_entity: &'static str,
115        to_entity: &'static str,
116        from_key: &'static str,
117        to_key: &'static str,
118        kind: RelationKind,
119    ) -> Self {
120        Self {
121            name,
122            from_entity,
123            to_entity,
124            from_key,
125            to_key,
126            kind,
127            join_table: None,
128            join_from_key: None,
129            join_to_key: None,
130        }
131    }
132
133    /// 创建 ManyToMany 关联关系定义(v2.3.0 新增)
134    ///
135    /// 强制 `kind = ManyToMany`,并设置中间表元数据。
136    ///
137    /// # 参数
138    ///
139    /// - `name`:关联名称(如 "roles")
140    /// - `from_entity`:源实体表名(如 "users")
141    /// - `to_entity`:目标实体表名(如 "roles")
142    /// - `from_key`:源实体主键列名(如 "id")
143    /// - `to_key`:目标实体主键列名(如 "id")
144    /// - `join_table`:中间表名(如 "user_roles")
145    /// - `join_from_key`:中间表中指向源实体的外键(如 "user_id")
146    /// - `join_to_key`:中间表中指向目标实体的外键(如 "role_id")
147    ///
148    /// ```ignore
149    /// let rel = RelationDef::new_many_to_many(
150    ///     "roles", "users", "roles", "id", "id",
151    ///     "user_roles", "user_id", "role_id",
152    /// );
153    /// assert_eq!(rel.kind, RelationKind::ManyToMany);
154    /// assert_eq!(rel.join_table, Some("user_roles"));
155    /// ```
156    #[allow(clippy::too_many_arguments)]
157    pub const fn new_many_to_many(
158        name: &'static str,
159        from_entity: &'static str,
160        to_entity: &'static str,
161        from_key: &'static str,
162        to_key: &'static str,
163        join_table: &'static str,
164        join_from_key: &'static str,
165        join_to_key: &'static str,
166    ) -> Self {
167        Self {
168            name,
169            from_entity,
170            to_entity,
171            from_key,
172            to_key,
173            kind: RelationKind::ManyToMany,
174            join_table: Some(join_table),
175            join_from_key: Some(join_from_key),
176            join_to_key: Some(join_to_key),
177        }
178    }
179}
180
181/// 关联关系 trait — 由 `#[derive(Relation)]` 自动实现
182///
183/// 提供关联定义访问和批量关联查询能力。
184/// 实体类型实现此 trait 后,可通过 `QueryBuilder::join()` 链式构建 JOIN 查询。
185pub trait RelationTrait: Send + Sync {
186    /// 返回当前关联的定义
187    fn def(&self) -> &'static RelationDef;
188
189    /// 返回实体所有关联定义的静态切片
190    fn all_relations() -> &'static [RelationDef]
191    where
192        Self: Sized;
193
194    /// 按名称查找关联定义
195    fn relation_by_name(name: &str) -> Option<&'static RelationDef>
196    where
197        Self: Sized,
198    {
199        Self::all_relations().iter().find(|r| r.name == name)
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use super::*;
206
207    static TEST_RELATIONS: &[RelationDef] = &[
208        RelationDef::new(
209            "orders",
210            "users",
211            "orders",
212            "id",
213            "user_id",
214            RelationKind::HasMany,
215        ),
216        RelationDef::new(
217            "profile",
218            "users",
219            "profiles",
220            "id",
221            "user_id",
222            RelationKind::HasOne,
223        ),
224        RelationDef::new(
225            "owner",
226            "orders",
227            "users",
228            "user_id",
229            "id",
230            RelationKind::BelongsTo,
231        ),
232        RelationDef::new(
233            "roles",
234            "users",
235            "roles",
236            "id",
237            "role_id",
238            RelationKind::ManyToMany,
239        ),
240    ];
241
242    struct User;
243
244    impl RelationTrait for User {
245        fn def(&self) -> &'static RelationDef {
246            &TEST_RELATIONS[0]
247        }
248        fn all_relations() -> &'static [RelationDef] {
249            TEST_RELATIONS
250        }
251    }
252
253    #[test]
254    fn test_relation_kind_default_join_type() {
255        assert_eq!(RelationKind::HasOne.default_join_type(), JoinKind::Inner);
256        assert_eq!(RelationKind::BelongsTo.default_join_type(), JoinKind::Inner);
257        assert_eq!(RelationKind::HasMany.default_join_type(), JoinKind::Left);
258        assert_eq!(RelationKind::ManyToMany.default_join_type(), JoinKind::Left);
259    }
260
261    #[test]
262    fn test_join_kind_as_sql() {
263        assert_eq!(JoinKind::Inner.as_sql(), "INNER JOIN");
264        assert_eq!(JoinKind::Left.as_sql(), "LEFT JOIN");
265    }
266
267    #[test]
268    fn test_relation_def_new() {
269        let def = RelationDef::new(
270            "orders",
271            "users",
272            "orders",
273            "id",
274            "user_id",
275            RelationKind::HasMany,
276        );
277        assert_eq!(def.name, "orders");
278        assert_eq!(def.from_entity, "users");
279        assert_eq!(def.to_entity, "orders");
280        assert_eq!(def.from_key, "id");
281        assert_eq!(def.to_key, "user_id");
282        assert_eq!(def.kind, RelationKind::HasMany);
283    }
284
285    #[test]
286    fn test_relation_trait_all_relations() {
287        let relations = User::all_relations();
288        assert_eq!(relations.len(), 4);
289        assert_eq!(relations[0].name, "orders");
290        assert_eq!(relations[1].name, "profile");
291        assert_eq!(relations[2].name, "owner");
292        assert_eq!(relations[3].name, "roles");
293    }
294
295    #[test]
296    fn test_relation_trait_relation_by_name() {
297        let found = User::relation_by_name("orders");
298        assert!(found.is_some());
299        assert_eq!(found.unwrap().to_entity, "orders");
300        assert_eq!(found.unwrap().kind, RelationKind::HasMany);
301
302        let not_found = User::relation_by_name("unknown");
303        assert!(not_found.is_none());
304    }
305
306    #[test]
307    fn test_relation_trait_def() {
308        let user = User;
309        let def = user.def();
310        assert_eq!(def.name, "orders");
311        assert_eq!(def.kind, RelationKind::HasMany);
312    }
313}