1use serde::{Deserialize, Serialize};
4use smol_str::SmolStr;
5
6use super::ReferentialAction;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10pub enum RelationType {
11 OneToOne,
13 OneToMany,
15 ManyToOne,
17 ManyToMany,
19}
20
21impl RelationType {
22 pub fn is_to_one(&self) -> bool {
24 matches!(self, Self::OneToOne | Self::ManyToOne)
25 }
26
27 pub fn is_to_many(&self) -> bool {
29 matches!(self, Self::OneToMany | Self::ManyToMany)
30 }
31
32 pub fn is_from_many(&self) -> bool {
34 matches!(self, Self::ManyToOne | Self::ManyToMany)
35 }
36}
37
38impl std::fmt::Display for RelationType {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 match self {
41 Self::OneToOne => write!(f, "1:1"),
42 Self::OneToMany => write!(f, "1:n"),
43 Self::ManyToOne => write!(f, "n:1"),
44 Self::ManyToMany => write!(f, "m:n"),
45 }
46 }
47}
48
49#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
51pub struct Relation {
52 pub name: Option<SmolStr>,
54 pub from_model: SmolStr,
56 pub from_field: SmolStr,
58 pub from_fields: Vec<SmolStr>,
60 pub to_model: SmolStr,
62 pub to_field: Option<SmolStr>,
64 pub to_fields: Vec<SmolStr>,
66 pub relation_type: RelationType,
68 pub on_delete: Option<ReferentialAction>,
70 pub on_update: Option<ReferentialAction>,
72}
73
74impl Relation {
75 pub fn new(
77 from_model: impl Into<SmolStr>,
78 from_field: impl Into<SmolStr>,
79 to_model: impl Into<SmolStr>,
80 relation_type: RelationType,
81 ) -> Self {
82 Self {
83 name: None,
84 from_model: from_model.into(),
85 from_field: from_field.into(),
86 from_fields: vec![],
87 to_model: to_model.into(),
88 to_field: None,
89 to_fields: vec![],
90 relation_type,
91 on_delete: None,
92 on_update: None,
93 }
94 }
95
96 pub fn with_name(mut self, name: impl Into<SmolStr>) -> Self {
98 self.name = Some(name.into());
99 self
100 }
101
102 pub fn with_from_fields(mut self, fields: Vec<SmolStr>) -> Self {
104 self.from_fields = fields;
105 self
106 }
107
108 pub fn with_to_fields(mut self, fields: Vec<SmolStr>) -> Self {
110 self.to_fields = fields;
111 self
112 }
113
114 pub fn with_to_field(mut self, field: impl Into<SmolStr>) -> Self {
116 self.to_field = Some(field.into());
117 self
118 }
119
120 pub fn with_on_delete(mut self, action: ReferentialAction) -> Self {
122 self.on_delete = Some(action);
123 self
124 }
125
126 pub fn with_on_update(mut self, action: ReferentialAction) -> Self {
128 self.on_update = Some(action);
129 self
130 }
131
132 pub fn is_implicit_many_to_many(&self) -> bool {
134 self.relation_type == RelationType::ManyToMany && self.from_fields.is_empty()
135 }
136
137 pub fn join_table_name(&self) -> Option<String> {
139 if self.relation_type != RelationType::ManyToMany {
140 return None;
141 }
142
143 let mut names = [self.from_model.as_str(), self.to_model.as_str()];
145 names.sort();
146
147 Some(format!("_{}_to_{}", names[0], names[1]))
148 }
149}
150
151#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
153pub struct Index {
154 pub name: Option<SmolStr>,
156 pub fields: Vec<IndexField>,
158 pub is_unique: bool,
160 pub index_type: Option<IndexType>,
162}
163
164impl Index {
165 pub fn new(fields: Vec<IndexField>) -> Self {
167 Self {
168 name: None,
169 fields,
170 is_unique: false,
171 index_type: None,
172 }
173 }
174
175 pub fn unique(fields: Vec<IndexField>) -> Self {
177 Self {
178 name: None,
179 fields,
180 is_unique: true,
181 index_type: None,
182 }
183 }
184
185 pub fn with_name(mut self, name: impl Into<SmolStr>) -> Self {
187 self.name = Some(name.into());
188 self
189 }
190
191 pub fn with_type(mut self, index_type: IndexType) -> Self {
193 self.index_type = Some(index_type);
194 self
195 }
196}
197
198#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
200pub struct IndexField {
201 pub name: SmolStr,
203 pub sort: SortOrder,
205}
206
207impl IndexField {
208 pub fn asc(name: impl Into<SmolStr>) -> Self {
210 Self {
211 name: name.into(),
212 sort: SortOrder::Asc,
213 }
214 }
215
216 pub fn desc(name: impl Into<SmolStr>) -> Self {
218 Self {
219 name: name.into(),
220 sort: SortOrder::Desc,
221 }
222 }
223}
224
225#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
227pub enum SortOrder {
228 #[default]
230 Asc,
231 Desc,
233}
234
235#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
237pub enum IndexType {
238 BTree,
240 Hash,
242 Gist,
244 Gin,
246 FullText,
248}
249
250impl IndexType {
251 #[allow(clippy::should_implement_trait)]
253 pub fn from_str(s: &str) -> Option<Self> {
254 match s.to_lowercase().as_str() {
255 "btree" => Some(Self::BTree),
256 "hash" => Some(Self::Hash),
257 "gist" => Some(Self::Gist),
258 "gin" => Some(Self::Gin),
259 "fulltext" => Some(Self::FullText),
260 _ => None,
261 }
262 }
263}
264
265#[cfg(test)]
266mod tests {
267 use super::*;
268
269 #[test]
272 fn test_relation_type_one_to_one() {
273 let rt = RelationType::OneToOne;
274 assert!(rt.is_to_one());
275 assert!(!rt.is_to_many());
276 assert!(!rt.is_from_many());
277 }
278
279 #[test]
280 fn test_relation_type_one_to_many() {
281 let rt = RelationType::OneToMany;
282 assert!(!rt.is_to_one());
283 assert!(rt.is_to_many());
284 assert!(!rt.is_from_many());
285 }
286
287 #[test]
288 fn test_relation_type_many_to_one() {
289 let rt = RelationType::ManyToOne;
290 assert!(rt.is_to_one());
291 assert!(!rt.is_to_many());
292 assert!(rt.is_from_many());
293 }
294
295 #[test]
296 fn test_relation_type_many_to_many() {
297 let rt = RelationType::ManyToMany;
298 assert!(!rt.is_to_one());
299 assert!(rt.is_to_many());
300 assert!(rt.is_from_many());
301 }
302
303 #[test]
304 fn test_relation_type_display() {
305 assert_eq!(format!("{}", RelationType::OneToOne), "1:1");
306 assert_eq!(format!("{}", RelationType::OneToMany), "1:n");
307 assert_eq!(format!("{}", RelationType::ManyToOne), "n:1");
308 assert_eq!(format!("{}", RelationType::ManyToMany), "m:n");
309 }
310
311 #[test]
312 fn test_relation_type_equality() {
313 assert_eq!(RelationType::OneToOne, RelationType::OneToOne);
314 assert_ne!(RelationType::OneToOne, RelationType::OneToMany);
315 }
316
317 #[test]
320 fn test_relation_new() {
321 let rel = Relation::new("Post", "author", "User", RelationType::ManyToOne);
322
323 assert!(rel.name.is_none());
324 assert_eq!(rel.from_model.as_str(), "Post");
325 assert_eq!(rel.from_field.as_str(), "author");
326 assert_eq!(rel.to_model.as_str(), "User");
327 assert!(rel.to_field.is_none());
328 assert_eq!(rel.relation_type, RelationType::ManyToOne);
329 assert!(rel.on_delete.is_none());
330 assert!(rel.on_update.is_none());
331 }
332
333 #[test]
334 fn test_relation_with_name() {
335 let rel = Relation::new("Post", "author", "User", RelationType::ManyToOne)
336 .with_name("PostAuthor");
337
338 assert_eq!(rel.name, Some("PostAuthor".into()));
339 }
340
341 #[test]
342 fn test_relation_with_from_fields() {
343 let rel = Relation::new("Post", "author", "User", RelationType::ManyToOne)
344 .with_from_fields(vec!["author_id".into()]);
345
346 assert_eq!(rel.from_fields, vec!["author_id".to_string()]);
347 }
348
349 #[test]
350 fn test_relation_with_to_fields() {
351 let rel = Relation::new("Post", "author", "User", RelationType::ManyToOne)
352 .with_to_fields(vec!["id".into()]);
353
354 assert_eq!(rel.to_fields, vec!["id".to_string()]);
355 }
356
357 #[test]
358 fn test_relation_with_to_field() {
359 let rel =
360 Relation::new("Post", "author", "User", RelationType::ManyToOne).with_to_field("posts");
361
362 assert_eq!(rel.to_field, Some("posts".into()));
363 }
364
365 #[test]
366 fn test_relation_with_on_delete() {
367 let rel = Relation::new("Post", "author", "User", RelationType::ManyToOne)
368 .with_on_delete(ReferentialAction::Cascade);
369
370 assert_eq!(rel.on_delete, Some(ReferentialAction::Cascade));
371 }
372
373 #[test]
374 fn test_relation_with_on_update() {
375 let rel = Relation::new("Post", "author", "User", RelationType::ManyToOne)
376 .with_on_update(ReferentialAction::Restrict);
377
378 assert_eq!(rel.on_update, Some(ReferentialAction::Restrict));
379 }
380
381 #[test]
382 fn test_relation_is_implicit_many_to_many_true() {
383 let rel = Relation::new("Post", "tags", "Tag", RelationType::ManyToMany);
384 assert!(rel.is_implicit_many_to_many());
385 }
386
387 #[test]
388 fn test_relation_is_implicit_many_to_many_false_explicit() {
389 let rel = Relation::new("Post", "tags", "Tag", RelationType::ManyToMany)
390 .with_from_fields(vec!["post_id".into()]);
391 assert!(!rel.is_implicit_many_to_many());
392 }
393
394 #[test]
395 fn test_relation_is_implicit_many_to_many_false_not_mtm() {
396 let rel = Relation::new("Post", "author", "User", RelationType::ManyToOne);
397 assert!(!rel.is_implicit_many_to_many());
398 }
399
400 #[test]
401 fn test_relation_join_table_name_mtm() {
402 let rel = Relation::new("Post", "tags", "Tag", RelationType::ManyToMany);
403 assert_eq!(rel.join_table_name(), Some("_Post_to_Tag".to_string()));
404 }
405
406 #[test]
407 fn test_relation_join_table_name_mtm_sorted() {
408 let rel = Relation::new("Tag", "posts", "Post", RelationType::ManyToMany);
410 assert_eq!(rel.join_table_name(), Some("_Post_to_Tag".to_string()));
411 }
412
413 #[test]
414 fn test_relation_join_table_name_not_mtm() {
415 let rel = Relation::new("Post", "author", "User", RelationType::ManyToOne);
416 assert!(rel.join_table_name().is_none());
417 }
418
419 #[test]
420 fn test_relation_builder_chain() {
421 let rel = Relation::new("Post", "author", "User", RelationType::ManyToOne)
422 .with_name("PostAuthor")
423 .with_from_fields(vec!["author_id".into()])
424 .with_to_fields(vec!["id".into()])
425 .with_to_field("posts")
426 .with_on_delete(ReferentialAction::Cascade)
427 .with_on_update(ReferentialAction::Restrict);
428
429 assert_eq!(rel.name, Some("PostAuthor".into()));
430 assert_eq!(rel.from_fields.len(), 1);
431 assert_eq!(rel.to_fields.len(), 1);
432 assert!(rel.to_field.is_some());
433 assert!(rel.on_delete.is_some());
434 assert!(rel.on_update.is_some());
435 }
436
437 #[test]
438 fn test_relation_equality() {
439 let rel1 = Relation::new("Post", "author", "User", RelationType::ManyToOne);
440 let rel2 = Relation::new("Post", "author", "User", RelationType::ManyToOne);
441
442 assert_eq!(rel1, rel2);
443 }
444
445 #[test]
448 fn test_index_new() {
449 let idx = Index::new(vec![IndexField::asc("email")]);
450
451 assert!(idx.name.is_none());
452 assert_eq!(idx.fields.len(), 1);
453 assert!(!idx.is_unique);
454 assert!(idx.index_type.is_none());
455 }
456
457 #[test]
458 fn test_index_unique() {
459 let idx = Index::unique(vec![IndexField::asc("email")]);
460
461 assert!(idx.is_unique);
462 }
463
464 #[test]
465 fn test_index_with_name() {
466 let idx = Index::new(vec![IndexField::asc("email")]).with_name("idx_user_email");
467
468 assert_eq!(idx.name, Some("idx_user_email".into()));
469 }
470
471 #[test]
472 fn test_index_with_type() {
473 let idx = Index::new(vec![IndexField::asc("data")]).with_type(IndexType::Gin);
474
475 assert_eq!(idx.index_type, Some(IndexType::Gin));
476 }
477
478 #[test]
479 fn test_index_multiple_fields() {
480 let idx = Index::unique(vec![
481 IndexField::asc("first_name"),
482 IndexField::asc("last_name"),
483 ]);
484
485 assert_eq!(idx.fields.len(), 2);
486 }
487
488 #[test]
491 fn test_index_field_asc() {
492 let field = IndexField::asc("email");
493
494 assert_eq!(field.name.as_str(), "email");
495 assert_eq!(field.sort, SortOrder::Asc);
496 }
497
498 #[test]
499 fn test_index_field_desc() {
500 let field = IndexField::desc("created_at");
501
502 assert_eq!(field.name.as_str(), "created_at");
503 assert_eq!(field.sort, SortOrder::Desc);
504 }
505
506 #[test]
507 fn test_index_field_equality() {
508 let f1 = IndexField::asc("email");
509 let f2 = IndexField::asc("email");
510 let f3 = IndexField::desc("email");
511
512 assert_eq!(f1, f2);
513 assert_ne!(f1, f3);
514 }
515
516 #[test]
519 fn test_sort_order_default() {
520 let order = SortOrder::default();
521 assert_eq!(order, SortOrder::Asc);
522 }
523
524 #[test]
525 fn test_sort_order_equality() {
526 assert_eq!(SortOrder::Asc, SortOrder::Asc);
527 assert_eq!(SortOrder::Desc, SortOrder::Desc);
528 assert_ne!(SortOrder::Asc, SortOrder::Desc);
529 }
530
531 #[test]
534 fn test_index_type_from_str_btree() {
535 assert_eq!(IndexType::from_str("btree"), Some(IndexType::BTree));
536 assert_eq!(IndexType::from_str("BTree"), Some(IndexType::BTree));
537 assert_eq!(IndexType::from_str("BTREE"), Some(IndexType::BTree));
538 }
539
540 #[test]
541 fn test_index_type_from_str_hash() {
542 assert_eq!(IndexType::from_str("hash"), Some(IndexType::Hash));
543 assert_eq!(IndexType::from_str("Hash"), Some(IndexType::Hash));
544 }
545
546 #[test]
547 fn test_index_type_from_str_gist() {
548 assert_eq!(IndexType::from_str("gist"), Some(IndexType::Gist));
549 assert_eq!(IndexType::from_str("GiST"), Some(IndexType::Gist));
550 }
551
552 #[test]
553 fn test_index_type_from_str_gin() {
554 assert_eq!(IndexType::from_str("gin"), Some(IndexType::Gin));
555 assert_eq!(IndexType::from_str("GIN"), Some(IndexType::Gin));
556 }
557
558 #[test]
559 fn test_index_type_from_str_fulltext() {
560 assert_eq!(IndexType::from_str("fulltext"), Some(IndexType::FullText));
561 assert_eq!(IndexType::from_str("FullText"), Some(IndexType::FullText));
562 }
563
564 #[test]
565 fn test_index_type_from_str_unknown() {
566 assert_eq!(IndexType::from_str("unknown"), None);
567 assert_eq!(IndexType::from_str(""), None);
568 }
569
570 #[test]
571 fn test_index_type_equality() {
572 assert_eq!(IndexType::BTree, IndexType::BTree);
573 assert_ne!(IndexType::BTree, IndexType::Hash);
574 }
575}