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 pub map: Option<SmolStr>,
74}
75
76impl Relation {
77 pub fn new(
79 from_model: impl Into<SmolStr>,
80 from_field: impl Into<SmolStr>,
81 to_model: impl Into<SmolStr>,
82 relation_type: RelationType,
83 ) -> Self {
84 Self {
85 name: None,
86 from_model: from_model.into(),
87 from_field: from_field.into(),
88 from_fields: vec![],
89 to_model: to_model.into(),
90 to_field: None,
91 to_fields: vec![],
92 relation_type,
93 on_delete: None,
94 on_update: None,
95 map: None,
96 }
97 }
98
99 pub fn with_name(mut self, name: impl Into<SmolStr>) -> Self {
101 self.name = Some(name.into());
102 self
103 }
104
105 pub fn with_from_fields(mut self, fields: Vec<SmolStr>) -> Self {
107 self.from_fields = fields;
108 self
109 }
110
111 pub fn with_to_fields(mut self, fields: Vec<SmolStr>) -> Self {
113 self.to_fields = fields;
114 self
115 }
116
117 pub fn with_to_field(mut self, field: impl Into<SmolStr>) -> Self {
119 self.to_field = Some(field.into());
120 self
121 }
122
123 pub fn with_on_delete(mut self, action: ReferentialAction) -> Self {
125 self.on_delete = Some(action);
126 self
127 }
128
129 pub fn with_on_update(mut self, action: ReferentialAction) -> Self {
131 self.on_update = Some(action);
132 self
133 }
134
135 pub fn with_map(mut self, name: impl Into<SmolStr>) -> Self {
137 self.map = Some(name.into());
138 self
139 }
140
141 pub fn is_implicit_many_to_many(&self) -> bool {
143 self.relation_type == RelationType::ManyToMany && self.from_fields.is_empty()
144 }
145
146 pub fn join_table_name(&self) -> Option<String> {
148 if self.relation_type != RelationType::ManyToMany {
149 return None;
150 }
151
152 let mut names = [self.from_model.as_str(), self.to_model.as_str()];
154 names.sort();
155
156 Some(format!("_{}_to_{}", names[0], names[1]))
157 }
158}
159
160#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
162pub struct Index {
163 pub name: Option<SmolStr>,
165 pub fields: Vec<IndexField>,
167 pub is_unique: bool,
169 pub index_type: Option<IndexType>,
171 pub vector_ops: Option<VectorOps>,
173 pub hnsw_m: Option<u32>,
175 pub hnsw_ef_construction: Option<u32>,
177 pub ivfflat_lists: Option<u32>,
179}
180
181impl Index {
182 pub fn new(fields: Vec<IndexField>) -> Self {
184 Self {
185 name: None,
186 fields,
187 is_unique: false,
188 index_type: None,
189 vector_ops: None,
190 hnsw_m: None,
191 hnsw_ef_construction: None,
192 ivfflat_lists: None,
193 }
194 }
195
196 pub fn unique(fields: Vec<IndexField>) -> Self {
198 Self {
199 name: None,
200 fields,
201 is_unique: true,
202 index_type: None,
203 vector_ops: None,
204 hnsw_m: None,
205 hnsw_ef_construction: None,
206 ivfflat_lists: None,
207 }
208 }
209
210 pub fn with_name(mut self, name: impl Into<SmolStr>) -> Self {
212 self.name = Some(name.into());
213 self
214 }
215
216 pub fn with_type(mut self, index_type: IndexType) -> Self {
218 self.index_type = Some(index_type);
219 self
220 }
221
222 pub fn with_vector_ops(mut self, ops: VectorOps) -> Self {
224 self.vector_ops = Some(ops);
225 self
226 }
227
228 pub fn with_hnsw_m(mut self, m: u32) -> Self {
230 self.hnsw_m = Some(m);
231 self
232 }
233
234 pub fn with_hnsw_ef_construction(mut self, ef: u32) -> Self {
236 self.hnsw_ef_construction = Some(ef);
237 self
238 }
239
240 pub fn with_ivfflat_lists(mut self, lists: u32) -> Self {
242 self.ivfflat_lists = Some(lists);
243 self
244 }
245
246 pub fn is_vector_index(&self) -> bool {
248 self.index_type
249 .as_ref()
250 .is_some_and(|t| t.is_vector_index())
251 }
252}
253
254#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
256pub struct IndexField {
257 pub name: SmolStr,
259 pub sort: SortOrder,
261}
262
263impl IndexField {
264 pub fn asc(name: impl Into<SmolStr>) -> Self {
266 Self {
267 name: name.into(),
268 sort: SortOrder::Asc,
269 }
270 }
271
272 pub fn desc(name: impl Into<SmolStr>) -> Self {
274 Self {
275 name: name.into(),
276 sort: SortOrder::Desc,
277 }
278 }
279}
280
281#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
283pub enum SortOrder {
284 #[default]
286 Asc,
287 Desc,
289}
290
291#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
293pub enum IndexType {
294 BTree,
296 Hash,
298 Gist,
300 Gin,
302 FullText,
304 Brin,
306 Hnsw,
308 IvfFlat,
310}
311
312impl IndexType {
313 #[allow(clippy::should_implement_trait)]
315 pub fn from_str(s: &str) -> Option<Self> {
316 match s.to_lowercase().as_str() {
317 "btree" => Some(Self::BTree),
318 "hash" => Some(Self::Hash),
319 "gist" => Some(Self::Gist),
320 "gin" => Some(Self::Gin),
321 "fulltext" => Some(Self::FullText),
322 "brin" => Some(Self::Brin),
323 "hnsw" => Some(Self::Hnsw),
324 "ivfflat" => Some(Self::IvfFlat),
325 _ => None,
326 }
327 }
328
329 pub fn is_vector_index(&self) -> bool {
331 matches!(self, Self::Hnsw | Self::IvfFlat)
332 }
333
334 pub fn as_sql(&self) -> &'static str {
336 match self {
337 Self::BTree => "BTREE",
338 Self::Hash => "HASH",
339 Self::Gist => "GIST",
340 Self::Gin => "GIN",
341 Self::FullText => "GIN", Self::Brin => "BRIN",
343 Self::Hnsw => "hnsw",
344 Self::IvfFlat => "ivfflat",
345 }
346 }
347}
348
349#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
351pub enum VectorOps {
352 #[default]
354 Cosine,
355 L2,
357 InnerProduct,
359}
360
361impl VectorOps {
362 #[allow(clippy::should_implement_trait)]
364 pub fn from_str(s: &str) -> Option<Self> {
365 match s.to_lowercase().as_str() {
366 "cosine" | "vector_cosine_ops" => Some(Self::Cosine),
367 "l2" | "vector_l2_ops" | "euclidean" => Some(Self::L2),
368 "ip" | "inner_product" | "vector_ip_ops" | "innerproduct" => Some(Self::InnerProduct),
369 _ => None,
370 }
371 }
372
373 pub fn as_ops_class(&self) -> &'static str {
375 match self {
376 Self::Cosine => "vector_cosine_ops",
377 Self::L2 => "vector_l2_ops",
378 Self::InnerProduct => "vector_ip_ops",
379 }
380 }
381
382 pub fn as_operator(&self) -> &'static str {
384 match self {
385 Self::Cosine => "<=>",
386 Self::L2 => "<->",
387 Self::InnerProduct => "<#>",
388 }
389 }
390}
391
392#[cfg(test)]
393mod tests {
394 use super::*;
395
396 #[test]
399 fn test_relation_type_one_to_one() {
400 let rt = RelationType::OneToOne;
401 assert!(rt.is_to_one());
402 assert!(!rt.is_to_many());
403 assert!(!rt.is_from_many());
404 }
405
406 #[test]
407 fn test_relation_type_one_to_many() {
408 let rt = RelationType::OneToMany;
409 assert!(!rt.is_to_one());
410 assert!(rt.is_to_many());
411 assert!(!rt.is_from_many());
412 }
413
414 #[test]
415 fn test_relation_type_many_to_one() {
416 let rt = RelationType::ManyToOne;
417 assert!(rt.is_to_one());
418 assert!(!rt.is_to_many());
419 assert!(rt.is_from_many());
420 }
421
422 #[test]
423 fn test_relation_type_many_to_many() {
424 let rt = RelationType::ManyToMany;
425 assert!(!rt.is_to_one());
426 assert!(rt.is_to_many());
427 assert!(rt.is_from_many());
428 }
429
430 #[test]
431 fn test_relation_type_display() {
432 assert_eq!(format!("{}", RelationType::OneToOne), "1:1");
433 assert_eq!(format!("{}", RelationType::OneToMany), "1:n");
434 assert_eq!(format!("{}", RelationType::ManyToOne), "n:1");
435 assert_eq!(format!("{}", RelationType::ManyToMany), "m:n");
436 }
437
438 #[test]
439 fn test_relation_type_equality() {
440 assert_eq!(RelationType::OneToOne, RelationType::OneToOne);
441 assert_ne!(RelationType::OneToOne, RelationType::OneToMany);
442 }
443
444 #[test]
447 fn test_relation_new() {
448 let rel = Relation::new("Post", "author", "User", RelationType::ManyToOne);
449
450 assert!(rel.name.is_none());
451 assert_eq!(rel.from_model.as_str(), "Post");
452 assert_eq!(rel.from_field.as_str(), "author");
453 assert_eq!(rel.to_model.as_str(), "User");
454 assert!(rel.to_field.is_none());
455 assert_eq!(rel.relation_type, RelationType::ManyToOne);
456 assert!(rel.on_delete.is_none());
457 assert!(rel.on_update.is_none());
458 }
459
460 #[test]
461 fn test_relation_with_name() {
462 let rel = Relation::new("Post", "author", "User", RelationType::ManyToOne)
463 .with_name("PostAuthor");
464
465 assert_eq!(rel.name, Some("PostAuthor".into()));
466 }
467
468 #[test]
469 fn test_relation_with_from_fields() {
470 let rel = Relation::new("Post", "author", "User", RelationType::ManyToOne)
471 .with_from_fields(vec!["author_id".into()]);
472
473 assert_eq!(rel.from_fields, vec!["author_id".to_string()]);
474 }
475
476 #[test]
477 fn test_relation_with_to_fields() {
478 let rel = Relation::new("Post", "author", "User", RelationType::ManyToOne)
479 .with_to_fields(vec!["id".into()]);
480
481 assert_eq!(rel.to_fields, vec!["id".to_string()]);
482 }
483
484 #[test]
485 fn test_relation_with_to_field() {
486 let rel =
487 Relation::new("Post", "author", "User", RelationType::ManyToOne).with_to_field("posts");
488
489 assert_eq!(rel.to_field, Some("posts".into()));
490 }
491
492 #[test]
493 fn test_relation_with_on_delete() {
494 let rel = Relation::new("Post", "author", "User", RelationType::ManyToOne)
495 .with_on_delete(ReferentialAction::Cascade);
496
497 assert_eq!(rel.on_delete, Some(ReferentialAction::Cascade));
498 }
499
500 #[test]
501 fn test_relation_with_on_update() {
502 let rel = Relation::new("Post", "author", "User", RelationType::ManyToOne)
503 .with_on_update(ReferentialAction::Restrict);
504
505 assert_eq!(rel.on_update, Some(ReferentialAction::Restrict));
506 }
507
508 #[test]
509 fn test_relation_is_implicit_many_to_many_true() {
510 let rel = Relation::new("Post", "tags", "Tag", RelationType::ManyToMany);
511 assert!(rel.is_implicit_many_to_many());
512 }
513
514 #[test]
515 fn test_relation_is_implicit_many_to_many_false_explicit() {
516 let rel = Relation::new("Post", "tags", "Tag", RelationType::ManyToMany)
517 .with_from_fields(vec!["post_id".into()]);
518 assert!(!rel.is_implicit_many_to_many());
519 }
520
521 #[test]
522 fn test_relation_is_implicit_many_to_many_false_not_mtm() {
523 let rel = Relation::new("Post", "author", "User", RelationType::ManyToOne);
524 assert!(!rel.is_implicit_many_to_many());
525 }
526
527 #[test]
528 fn test_relation_join_table_name_mtm() {
529 let rel = Relation::new("Post", "tags", "Tag", RelationType::ManyToMany);
530 assert_eq!(rel.join_table_name(), Some("_Post_to_Tag".to_string()));
531 }
532
533 #[test]
534 fn test_relation_join_table_name_mtm_sorted() {
535 let rel = Relation::new("Tag", "posts", "Post", RelationType::ManyToMany);
537 assert_eq!(rel.join_table_name(), Some("_Post_to_Tag".to_string()));
538 }
539
540 #[test]
541 fn test_relation_join_table_name_not_mtm() {
542 let rel = Relation::new("Post", "author", "User", RelationType::ManyToOne);
543 assert!(rel.join_table_name().is_none());
544 }
545
546 #[test]
547 fn test_relation_builder_chain() {
548 let rel = Relation::new("Post", "author", "User", RelationType::ManyToOne)
549 .with_name("PostAuthor")
550 .with_from_fields(vec!["author_id".into()])
551 .with_to_fields(vec!["id".into()])
552 .with_to_field("posts")
553 .with_on_delete(ReferentialAction::Cascade)
554 .with_on_update(ReferentialAction::Restrict);
555
556 assert_eq!(rel.name, Some("PostAuthor".into()));
557 assert_eq!(rel.from_fields.len(), 1);
558 assert_eq!(rel.to_fields.len(), 1);
559 assert!(rel.to_field.is_some());
560 assert!(rel.on_delete.is_some());
561 assert!(rel.on_update.is_some());
562 }
563
564 #[test]
565 fn test_relation_equality() {
566 let rel1 = Relation::new("Post", "author", "User", RelationType::ManyToOne);
567 let rel2 = Relation::new("Post", "author", "User", RelationType::ManyToOne);
568
569 assert_eq!(rel1, rel2);
570 }
571
572 #[test]
575 fn test_index_new() {
576 let idx = Index::new(vec![IndexField::asc("email")]);
577
578 assert!(idx.name.is_none());
579 assert_eq!(idx.fields.len(), 1);
580 assert!(!idx.is_unique);
581 assert!(idx.index_type.is_none());
582 }
583
584 #[test]
585 fn test_index_unique() {
586 let idx = Index::unique(vec![IndexField::asc("email")]);
587
588 assert!(idx.is_unique);
589 }
590
591 #[test]
592 fn test_index_with_name() {
593 let idx = Index::new(vec![IndexField::asc("email")]).with_name("idx_user_email");
594
595 assert_eq!(idx.name, Some("idx_user_email".into()));
596 }
597
598 #[test]
599 fn test_index_with_type() {
600 let idx = Index::new(vec![IndexField::asc("data")]).with_type(IndexType::Gin);
601
602 assert_eq!(idx.index_type, Some(IndexType::Gin));
603 }
604
605 #[test]
606 fn test_index_multiple_fields() {
607 let idx = Index::unique(vec![
608 IndexField::asc("first_name"),
609 IndexField::asc("last_name"),
610 ]);
611
612 assert_eq!(idx.fields.len(), 2);
613 }
614
615 #[test]
618 fn test_index_field_asc() {
619 let field = IndexField::asc("email");
620
621 assert_eq!(field.name.as_str(), "email");
622 assert_eq!(field.sort, SortOrder::Asc);
623 }
624
625 #[test]
626 fn test_index_field_desc() {
627 let field = IndexField::desc("created_at");
628
629 assert_eq!(field.name.as_str(), "created_at");
630 assert_eq!(field.sort, SortOrder::Desc);
631 }
632
633 #[test]
634 fn test_index_field_equality() {
635 let f1 = IndexField::asc("email");
636 let f2 = IndexField::asc("email");
637 let f3 = IndexField::desc("email");
638
639 assert_eq!(f1, f2);
640 assert_ne!(f1, f3);
641 }
642
643 #[test]
646 fn test_sort_order_default() {
647 let order = SortOrder::default();
648 assert_eq!(order, SortOrder::Asc);
649 }
650
651 #[test]
652 fn test_sort_order_equality() {
653 assert_eq!(SortOrder::Asc, SortOrder::Asc);
654 assert_eq!(SortOrder::Desc, SortOrder::Desc);
655 assert_ne!(SortOrder::Asc, SortOrder::Desc);
656 }
657
658 #[test]
661 fn test_index_type_from_str_btree() {
662 assert_eq!(IndexType::from_str("btree"), Some(IndexType::BTree));
663 assert_eq!(IndexType::from_str("BTree"), Some(IndexType::BTree));
664 assert_eq!(IndexType::from_str("BTREE"), Some(IndexType::BTree));
665 }
666
667 #[test]
668 fn test_index_type_from_str_hash() {
669 assert_eq!(IndexType::from_str("hash"), Some(IndexType::Hash));
670 assert_eq!(IndexType::from_str("Hash"), Some(IndexType::Hash));
671 }
672
673 #[test]
674 fn test_index_type_from_str_gist() {
675 assert_eq!(IndexType::from_str("gist"), Some(IndexType::Gist));
676 assert_eq!(IndexType::from_str("GiST"), Some(IndexType::Gist));
677 }
678
679 #[test]
680 fn test_index_type_from_str_gin() {
681 assert_eq!(IndexType::from_str("gin"), Some(IndexType::Gin));
682 assert_eq!(IndexType::from_str("GIN"), Some(IndexType::Gin));
683 }
684
685 #[test]
686 fn test_index_type_from_str_fulltext() {
687 assert_eq!(IndexType::from_str("fulltext"), Some(IndexType::FullText));
688 assert_eq!(IndexType::from_str("FullText"), Some(IndexType::FullText));
689 }
690
691 #[test]
692 fn test_index_type_from_str_unknown() {
693 assert_eq!(IndexType::from_str("unknown"), None);
694 assert_eq!(IndexType::from_str(""), None);
695 }
696
697 #[test]
698 fn test_index_type_equality() {
699 assert_eq!(IndexType::BTree, IndexType::BTree);
700 assert_ne!(IndexType::BTree, IndexType::Hash);
701 }
702
703 #[test]
704 fn test_index_type_from_str_brin() {
705 assert_eq!(IndexType::from_str("brin"), Some(IndexType::Brin));
706 assert_eq!(IndexType::from_str("BRIN"), Some(IndexType::Brin));
707 }
708
709 #[test]
710 fn test_index_type_from_str_hnsw() {
711 assert_eq!(IndexType::from_str("hnsw"), Some(IndexType::Hnsw));
712 assert_eq!(IndexType::from_str("HNSW"), Some(IndexType::Hnsw));
713 }
714
715 #[test]
716 fn test_index_type_from_str_ivfflat() {
717 assert_eq!(IndexType::from_str("ivfflat"), Some(IndexType::IvfFlat));
718 assert_eq!(IndexType::from_str("IVFFLAT"), Some(IndexType::IvfFlat));
719 }
720
721 #[test]
722 fn test_index_type_is_vector_index() {
723 assert!(IndexType::Hnsw.is_vector_index());
724 assert!(IndexType::IvfFlat.is_vector_index());
725 assert!(!IndexType::BTree.is_vector_index());
726 assert!(!IndexType::Gin.is_vector_index());
727 }
728
729 #[test]
730 fn test_index_type_as_sql() {
731 assert_eq!(IndexType::BTree.as_sql(), "BTREE");
732 assert_eq!(IndexType::Hash.as_sql(), "HASH");
733 assert_eq!(IndexType::Hnsw.as_sql(), "hnsw");
734 assert_eq!(IndexType::IvfFlat.as_sql(), "ivfflat");
735 }
736
737 #[test]
740 fn test_vector_ops_from_str_cosine() {
741 assert_eq!(VectorOps::from_str("cosine"), Some(VectorOps::Cosine));
742 assert_eq!(
743 VectorOps::from_str("vector_cosine_ops"),
744 Some(VectorOps::Cosine)
745 );
746 }
747
748 #[test]
749 fn test_vector_ops_from_str_l2() {
750 assert_eq!(VectorOps::from_str("l2"), Some(VectorOps::L2));
751 assert_eq!(VectorOps::from_str("euclidean"), Some(VectorOps::L2));
752 assert_eq!(VectorOps::from_str("vector_l2_ops"), Some(VectorOps::L2));
753 }
754
755 #[test]
756 fn test_vector_ops_from_str_inner_product() {
757 assert_eq!(VectorOps::from_str("ip"), Some(VectorOps::InnerProduct));
758 assert_eq!(
759 VectorOps::from_str("inner_product"),
760 Some(VectorOps::InnerProduct)
761 );
762 assert_eq!(
763 VectorOps::from_str("vector_ip_ops"),
764 Some(VectorOps::InnerProduct)
765 );
766 }
767
768 #[test]
769 fn test_vector_ops_as_ops_class() {
770 assert_eq!(VectorOps::Cosine.as_ops_class(), "vector_cosine_ops");
771 assert_eq!(VectorOps::L2.as_ops_class(), "vector_l2_ops");
772 assert_eq!(VectorOps::InnerProduct.as_ops_class(), "vector_ip_ops");
773 }
774
775 #[test]
776 fn test_vector_ops_as_operator() {
777 assert_eq!(VectorOps::Cosine.as_operator(), "<=>");
778 assert_eq!(VectorOps::L2.as_operator(), "<->");
779 assert_eq!(VectorOps::InnerProduct.as_operator(), "<#>");
780 }
781
782 #[test]
783 fn test_vector_ops_default() {
784 let ops = VectorOps::default();
785 assert_eq!(ops, VectorOps::Cosine);
786 }
787
788 #[test]
791 fn test_index_with_vector_ops() {
792 let idx = Index::new(vec![IndexField::asc("embedding")])
793 .with_type(IndexType::Hnsw)
794 .with_vector_ops(VectorOps::Cosine)
795 .with_hnsw_m(16)
796 .with_hnsw_ef_construction(64);
797
798 assert_eq!(idx.index_type, Some(IndexType::Hnsw));
799 assert_eq!(idx.vector_ops, Some(VectorOps::Cosine));
800 assert_eq!(idx.hnsw_m, Some(16));
801 assert_eq!(idx.hnsw_ef_construction, Some(64));
802 assert!(idx.is_vector_index());
803 }
804
805 #[test]
806 fn test_index_with_ivfflat() {
807 let idx = Index::new(vec![IndexField::asc("embedding")])
808 .with_type(IndexType::IvfFlat)
809 .with_vector_ops(VectorOps::L2)
810 .with_ivfflat_lists(100);
811
812 assert_eq!(idx.index_type, Some(IndexType::IvfFlat));
813 assert_eq!(idx.vector_ops, Some(VectorOps::L2));
814 assert_eq!(idx.ivfflat_lists, Some(100));
815 assert!(idx.is_vector_index());
816 }
817}