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