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 pub vector_ops: Option<VectorOps>,
164 pub hnsw_m: Option<u32>,
166 pub hnsw_ef_construction: Option<u32>,
168 pub ivfflat_lists: Option<u32>,
170}
171
172impl Index {
173 pub fn new(fields: Vec<IndexField>) -> Self {
175 Self {
176 name: None,
177 fields,
178 is_unique: false,
179 index_type: None,
180 vector_ops: None,
181 hnsw_m: None,
182 hnsw_ef_construction: None,
183 ivfflat_lists: None,
184 }
185 }
186
187 pub fn unique(fields: Vec<IndexField>) -> Self {
189 Self {
190 name: None,
191 fields,
192 is_unique: true,
193 index_type: None,
194 vector_ops: None,
195 hnsw_m: None,
196 hnsw_ef_construction: None,
197 ivfflat_lists: None,
198 }
199 }
200
201 pub fn with_name(mut self, name: impl Into<SmolStr>) -> Self {
203 self.name = Some(name.into());
204 self
205 }
206
207 pub fn with_type(mut self, index_type: IndexType) -> Self {
209 self.index_type = Some(index_type);
210 self
211 }
212
213 pub fn with_vector_ops(mut self, ops: VectorOps) -> Self {
215 self.vector_ops = Some(ops);
216 self
217 }
218
219 pub fn with_hnsw_m(mut self, m: u32) -> Self {
221 self.hnsw_m = Some(m);
222 self
223 }
224
225 pub fn with_hnsw_ef_construction(mut self, ef: u32) -> Self {
227 self.hnsw_ef_construction = Some(ef);
228 self
229 }
230
231 pub fn with_ivfflat_lists(mut self, lists: u32) -> Self {
233 self.ivfflat_lists = Some(lists);
234 self
235 }
236
237 pub fn is_vector_index(&self) -> bool {
239 self.index_type
240 .as_ref()
241 .is_some_and(|t| t.is_vector_index())
242 }
243}
244
245#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
247pub struct IndexField {
248 pub name: SmolStr,
250 pub sort: SortOrder,
252}
253
254impl IndexField {
255 pub fn asc(name: impl Into<SmolStr>) -> Self {
257 Self {
258 name: name.into(),
259 sort: SortOrder::Asc,
260 }
261 }
262
263 pub fn desc(name: impl Into<SmolStr>) -> Self {
265 Self {
266 name: name.into(),
267 sort: SortOrder::Desc,
268 }
269 }
270}
271
272#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
274pub enum SortOrder {
275 #[default]
277 Asc,
278 Desc,
280}
281
282#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
284pub enum IndexType {
285 BTree,
287 Hash,
289 Gist,
291 Gin,
293 FullText,
295 Brin,
297 Hnsw,
299 IvfFlat,
301}
302
303impl IndexType {
304 #[allow(clippy::should_implement_trait)]
306 pub fn from_str(s: &str) -> Option<Self> {
307 match s.to_lowercase().as_str() {
308 "btree" => Some(Self::BTree),
309 "hash" => Some(Self::Hash),
310 "gist" => Some(Self::Gist),
311 "gin" => Some(Self::Gin),
312 "fulltext" => Some(Self::FullText),
313 "brin" => Some(Self::Brin),
314 "hnsw" => Some(Self::Hnsw),
315 "ivfflat" => Some(Self::IvfFlat),
316 _ => None,
317 }
318 }
319
320 pub fn is_vector_index(&self) -> bool {
322 matches!(self, Self::Hnsw | Self::IvfFlat)
323 }
324
325 pub fn as_sql(&self) -> &'static str {
327 match self {
328 Self::BTree => "BTREE",
329 Self::Hash => "HASH",
330 Self::Gist => "GIST",
331 Self::Gin => "GIN",
332 Self::FullText => "GIN", Self::Brin => "BRIN",
334 Self::Hnsw => "hnsw",
335 Self::IvfFlat => "ivfflat",
336 }
337 }
338}
339
340#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
342pub enum VectorOps {
343 #[default]
345 Cosine,
346 L2,
348 InnerProduct,
350}
351
352impl VectorOps {
353 #[allow(clippy::should_implement_trait)]
355 pub fn from_str(s: &str) -> Option<Self> {
356 match s.to_lowercase().as_str() {
357 "cosine" | "vector_cosine_ops" => Some(Self::Cosine),
358 "l2" | "vector_l2_ops" | "euclidean" => Some(Self::L2),
359 "ip" | "inner_product" | "vector_ip_ops" | "innerproduct" => Some(Self::InnerProduct),
360 _ => None,
361 }
362 }
363
364 pub fn as_ops_class(&self) -> &'static str {
366 match self {
367 Self::Cosine => "vector_cosine_ops",
368 Self::L2 => "vector_l2_ops",
369 Self::InnerProduct => "vector_ip_ops",
370 }
371 }
372
373 pub fn as_operator(&self) -> &'static str {
375 match self {
376 Self::Cosine => "<=>",
377 Self::L2 => "<->",
378 Self::InnerProduct => "<#>",
379 }
380 }
381}
382
383#[cfg(test)]
384mod tests {
385 use super::*;
386
387 #[test]
390 fn test_relation_type_one_to_one() {
391 let rt = RelationType::OneToOne;
392 assert!(rt.is_to_one());
393 assert!(!rt.is_to_many());
394 assert!(!rt.is_from_many());
395 }
396
397 #[test]
398 fn test_relation_type_one_to_many() {
399 let rt = RelationType::OneToMany;
400 assert!(!rt.is_to_one());
401 assert!(rt.is_to_many());
402 assert!(!rt.is_from_many());
403 }
404
405 #[test]
406 fn test_relation_type_many_to_one() {
407 let rt = RelationType::ManyToOne;
408 assert!(rt.is_to_one());
409 assert!(!rt.is_to_many());
410 assert!(rt.is_from_many());
411 }
412
413 #[test]
414 fn test_relation_type_many_to_many() {
415 let rt = RelationType::ManyToMany;
416 assert!(!rt.is_to_one());
417 assert!(rt.is_to_many());
418 assert!(rt.is_from_many());
419 }
420
421 #[test]
422 fn test_relation_type_display() {
423 assert_eq!(format!("{}", RelationType::OneToOne), "1:1");
424 assert_eq!(format!("{}", RelationType::OneToMany), "1:n");
425 assert_eq!(format!("{}", RelationType::ManyToOne), "n:1");
426 assert_eq!(format!("{}", RelationType::ManyToMany), "m:n");
427 }
428
429 #[test]
430 fn test_relation_type_equality() {
431 assert_eq!(RelationType::OneToOne, RelationType::OneToOne);
432 assert_ne!(RelationType::OneToOne, RelationType::OneToMany);
433 }
434
435 #[test]
438 fn test_relation_new() {
439 let rel = Relation::new("Post", "author", "User", RelationType::ManyToOne);
440
441 assert!(rel.name.is_none());
442 assert_eq!(rel.from_model.as_str(), "Post");
443 assert_eq!(rel.from_field.as_str(), "author");
444 assert_eq!(rel.to_model.as_str(), "User");
445 assert!(rel.to_field.is_none());
446 assert_eq!(rel.relation_type, RelationType::ManyToOne);
447 assert!(rel.on_delete.is_none());
448 assert!(rel.on_update.is_none());
449 }
450
451 #[test]
452 fn test_relation_with_name() {
453 let rel = Relation::new("Post", "author", "User", RelationType::ManyToOne)
454 .with_name("PostAuthor");
455
456 assert_eq!(rel.name, Some("PostAuthor".into()));
457 }
458
459 #[test]
460 fn test_relation_with_from_fields() {
461 let rel = Relation::new("Post", "author", "User", RelationType::ManyToOne)
462 .with_from_fields(vec!["author_id".into()]);
463
464 assert_eq!(rel.from_fields, vec!["author_id".to_string()]);
465 }
466
467 #[test]
468 fn test_relation_with_to_fields() {
469 let rel = Relation::new("Post", "author", "User", RelationType::ManyToOne)
470 .with_to_fields(vec!["id".into()]);
471
472 assert_eq!(rel.to_fields, vec!["id".to_string()]);
473 }
474
475 #[test]
476 fn test_relation_with_to_field() {
477 let rel =
478 Relation::new("Post", "author", "User", RelationType::ManyToOne).with_to_field("posts");
479
480 assert_eq!(rel.to_field, Some("posts".into()));
481 }
482
483 #[test]
484 fn test_relation_with_on_delete() {
485 let rel = Relation::new("Post", "author", "User", RelationType::ManyToOne)
486 .with_on_delete(ReferentialAction::Cascade);
487
488 assert_eq!(rel.on_delete, Some(ReferentialAction::Cascade));
489 }
490
491 #[test]
492 fn test_relation_with_on_update() {
493 let rel = Relation::new("Post", "author", "User", RelationType::ManyToOne)
494 .with_on_update(ReferentialAction::Restrict);
495
496 assert_eq!(rel.on_update, Some(ReferentialAction::Restrict));
497 }
498
499 #[test]
500 fn test_relation_is_implicit_many_to_many_true() {
501 let rel = Relation::new("Post", "tags", "Tag", RelationType::ManyToMany);
502 assert!(rel.is_implicit_many_to_many());
503 }
504
505 #[test]
506 fn test_relation_is_implicit_many_to_many_false_explicit() {
507 let rel = Relation::new("Post", "tags", "Tag", RelationType::ManyToMany)
508 .with_from_fields(vec!["post_id".into()]);
509 assert!(!rel.is_implicit_many_to_many());
510 }
511
512 #[test]
513 fn test_relation_is_implicit_many_to_many_false_not_mtm() {
514 let rel = Relation::new("Post", "author", "User", RelationType::ManyToOne);
515 assert!(!rel.is_implicit_many_to_many());
516 }
517
518 #[test]
519 fn test_relation_join_table_name_mtm() {
520 let rel = Relation::new("Post", "tags", "Tag", RelationType::ManyToMany);
521 assert_eq!(rel.join_table_name(), Some("_Post_to_Tag".to_string()));
522 }
523
524 #[test]
525 fn test_relation_join_table_name_mtm_sorted() {
526 let rel = Relation::new("Tag", "posts", "Post", RelationType::ManyToMany);
528 assert_eq!(rel.join_table_name(), Some("_Post_to_Tag".to_string()));
529 }
530
531 #[test]
532 fn test_relation_join_table_name_not_mtm() {
533 let rel = Relation::new("Post", "author", "User", RelationType::ManyToOne);
534 assert!(rel.join_table_name().is_none());
535 }
536
537 #[test]
538 fn test_relation_builder_chain() {
539 let rel = Relation::new("Post", "author", "User", RelationType::ManyToOne)
540 .with_name("PostAuthor")
541 .with_from_fields(vec!["author_id".into()])
542 .with_to_fields(vec!["id".into()])
543 .with_to_field("posts")
544 .with_on_delete(ReferentialAction::Cascade)
545 .with_on_update(ReferentialAction::Restrict);
546
547 assert_eq!(rel.name, Some("PostAuthor".into()));
548 assert_eq!(rel.from_fields.len(), 1);
549 assert_eq!(rel.to_fields.len(), 1);
550 assert!(rel.to_field.is_some());
551 assert!(rel.on_delete.is_some());
552 assert!(rel.on_update.is_some());
553 }
554
555 #[test]
556 fn test_relation_equality() {
557 let rel1 = Relation::new("Post", "author", "User", RelationType::ManyToOne);
558 let rel2 = Relation::new("Post", "author", "User", RelationType::ManyToOne);
559
560 assert_eq!(rel1, rel2);
561 }
562
563 #[test]
566 fn test_index_new() {
567 let idx = Index::new(vec![IndexField::asc("email")]);
568
569 assert!(idx.name.is_none());
570 assert_eq!(idx.fields.len(), 1);
571 assert!(!idx.is_unique);
572 assert!(idx.index_type.is_none());
573 }
574
575 #[test]
576 fn test_index_unique() {
577 let idx = Index::unique(vec![IndexField::asc("email")]);
578
579 assert!(idx.is_unique);
580 }
581
582 #[test]
583 fn test_index_with_name() {
584 let idx = Index::new(vec![IndexField::asc("email")]).with_name("idx_user_email");
585
586 assert_eq!(idx.name, Some("idx_user_email".into()));
587 }
588
589 #[test]
590 fn test_index_with_type() {
591 let idx = Index::new(vec![IndexField::asc("data")]).with_type(IndexType::Gin);
592
593 assert_eq!(idx.index_type, Some(IndexType::Gin));
594 }
595
596 #[test]
597 fn test_index_multiple_fields() {
598 let idx = Index::unique(vec![
599 IndexField::asc("first_name"),
600 IndexField::asc("last_name"),
601 ]);
602
603 assert_eq!(idx.fields.len(), 2);
604 }
605
606 #[test]
609 fn test_index_field_asc() {
610 let field = IndexField::asc("email");
611
612 assert_eq!(field.name.as_str(), "email");
613 assert_eq!(field.sort, SortOrder::Asc);
614 }
615
616 #[test]
617 fn test_index_field_desc() {
618 let field = IndexField::desc("created_at");
619
620 assert_eq!(field.name.as_str(), "created_at");
621 assert_eq!(field.sort, SortOrder::Desc);
622 }
623
624 #[test]
625 fn test_index_field_equality() {
626 let f1 = IndexField::asc("email");
627 let f2 = IndexField::asc("email");
628 let f3 = IndexField::desc("email");
629
630 assert_eq!(f1, f2);
631 assert_ne!(f1, f3);
632 }
633
634 #[test]
637 fn test_sort_order_default() {
638 let order = SortOrder::default();
639 assert_eq!(order, SortOrder::Asc);
640 }
641
642 #[test]
643 fn test_sort_order_equality() {
644 assert_eq!(SortOrder::Asc, SortOrder::Asc);
645 assert_eq!(SortOrder::Desc, SortOrder::Desc);
646 assert_ne!(SortOrder::Asc, SortOrder::Desc);
647 }
648
649 #[test]
652 fn test_index_type_from_str_btree() {
653 assert_eq!(IndexType::from_str("btree"), Some(IndexType::BTree));
654 assert_eq!(IndexType::from_str("BTree"), Some(IndexType::BTree));
655 assert_eq!(IndexType::from_str("BTREE"), Some(IndexType::BTree));
656 }
657
658 #[test]
659 fn test_index_type_from_str_hash() {
660 assert_eq!(IndexType::from_str("hash"), Some(IndexType::Hash));
661 assert_eq!(IndexType::from_str("Hash"), Some(IndexType::Hash));
662 }
663
664 #[test]
665 fn test_index_type_from_str_gist() {
666 assert_eq!(IndexType::from_str("gist"), Some(IndexType::Gist));
667 assert_eq!(IndexType::from_str("GiST"), Some(IndexType::Gist));
668 }
669
670 #[test]
671 fn test_index_type_from_str_gin() {
672 assert_eq!(IndexType::from_str("gin"), Some(IndexType::Gin));
673 assert_eq!(IndexType::from_str("GIN"), Some(IndexType::Gin));
674 }
675
676 #[test]
677 fn test_index_type_from_str_fulltext() {
678 assert_eq!(IndexType::from_str("fulltext"), Some(IndexType::FullText));
679 assert_eq!(IndexType::from_str("FullText"), Some(IndexType::FullText));
680 }
681
682 #[test]
683 fn test_index_type_from_str_unknown() {
684 assert_eq!(IndexType::from_str("unknown"), None);
685 assert_eq!(IndexType::from_str(""), None);
686 }
687
688 #[test]
689 fn test_index_type_equality() {
690 assert_eq!(IndexType::BTree, IndexType::BTree);
691 assert_ne!(IndexType::BTree, IndexType::Hash);
692 }
693
694 #[test]
695 fn test_index_type_from_str_brin() {
696 assert_eq!(IndexType::from_str("brin"), Some(IndexType::Brin));
697 assert_eq!(IndexType::from_str("BRIN"), Some(IndexType::Brin));
698 }
699
700 #[test]
701 fn test_index_type_from_str_hnsw() {
702 assert_eq!(IndexType::from_str("hnsw"), Some(IndexType::Hnsw));
703 assert_eq!(IndexType::from_str("HNSW"), Some(IndexType::Hnsw));
704 }
705
706 #[test]
707 fn test_index_type_from_str_ivfflat() {
708 assert_eq!(IndexType::from_str("ivfflat"), Some(IndexType::IvfFlat));
709 assert_eq!(IndexType::from_str("IVFFLAT"), Some(IndexType::IvfFlat));
710 }
711
712 #[test]
713 fn test_index_type_is_vector_index() {
714 assert!(IndexType::Hnsw.is_vector_index());
715 assert!(IndexType::IvfFlat.is_vector_index());
716 assert!(!IndexType::BTree.is_vector_index());
717 assert!(!IndexType::Gin.is_vector_index());
718 }
719
720 #[test]
721 fn test_index_type_as_sql() {
722 assert_eq!(IndexType::BTree.as_sql(), "BTREE");
723 assert_eq!(IndexType::Hash.as_sql(), "HASH");
724 assert_eq!(IndexType::Hnsw.as_sql(), "hnsw");
725 assert_eq!(IndexType::IvfFlat.as_sql(), "ivfflat");
726 }
727
728 #[test]
731 fn test_vector_ops_from_str_cosine() {
732 assert_eq!(VectorOps::from_str("cosine"), Some(VectorOps::Cosine));
733 assert_eq!(
734 VectorOps::from_str("vector_cosine_ops"),
735 Some(VectorOps::Cosine)
736 );
737 }
738
739 #[test]
740 fn test_vector_ops_from_str_l2() {
741 assert_eq!(VectorOps::from_str("l2"), Some(VectorOps::L2));
742 assert_eq!(VectorOps::from_str("euclidean"), Some(VectorOps::L2));
743 assert_eq!(VectorOps::from_str("vector_l2_ops"), Some(VectorOps::L2));
744 }
745
746 #[test]
747 fn test_vector_ops_from_str_inner_product() {
748 assert_eq!(VectorOps::from_str("ip"), Some(VectorOps::InnerProduct));
749 assert_eq!(
750 VectorOps::from_str("inner_product"),
751 Some(VectorOps::InnerProduct)
752 );
753 assert_eq!(
754 VectorOps::from_str("vector_ip_ops"),
755 Some(VectorOps::InnerProduct)
756 );
757 }
758
759 #[test]
760 fn test_vector_ops_as_ops_class() {
761 assert_eq!(VectorOps::Cosine.as_ops_class(), "vector_cosine_ops");
762 assert_eq!(VectorOps::L2.as_ops_class(), "vector_l2_ops");
763 assert_eq!(VectorOps::InnerProduct.as_ops_class(), "vector_ip_ops");
764 }
765
766 #[test]
767 fn test_vector_ops_as_operator() {
768 assert_eq!(VectorOps::Cosine.as_operator(), "<=>");
769 assert_eq!(VectorOps::L2.as_operator(), "<->");
770 assert_eq!(VectorOps::InnerProduct.as_operator(), "<#>");
771 }
772
773 #[test]
774 fn test_vector_ops_default() {
775 let ops = VectorOps::default();
776 assert_eq!(ops, VectorOps::Cosine);
777 }
778
779 #[test]
782 fn test_index_with_vector_ops() {
783 let idx = Index::new(vec![IndexField::asc("embedding")])
784 .with_type(IndexType::Hnsw)
785 .with_vector_ops(VectorOps::Cosine)
786 .with_hnsw_m(16)
787 .with_hnsw_ef_construction(64);
788
789 assert_eq!(idx.index_type, Some(IndexType::Hnsw));
790 assert_eq!(idx.vector_ops, Some(VectorOps::Cosine));
791 assert_eq!(idx.hnsw_m, Some(16));
792 assert_eq!(idx.hnsw_ef_construction, Some(64));
793 assert!(idx.is_vector_index());
794 }
795
796 #[test]
797 fn test_index_with_ivfflat() {
798 let idx = Index::new(vec![IndexField::asc("embedding")])
799 .with_type(IndexType::IvfFlat)
800 .with_vector_ops(VectorOps::L2)
801 .with_ivfflat_lists(100);
802
803 assert_eq!(idx.index_type, Some(IndexType::IvfFlat));
804 assert_eq!(idx.vector_ops, Some(VectorOps::L2));
805 assert_eq!(idx.ivfflat_lists, Some(100));
806 assert!(idx.is_vector_index());
807 }
808}