1use std::collections::HashMap;
44use std::marker::PhantomData;
45
46use rainier_orm::sea_query::Value;
47use rainier_support::{str, Result};
48
49use crate::criteria::Criteria;
50use crate::model::Model;
51use crate::repository::Repository;
52
53#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
60pub struct RelationKey(String);
61
62impl RelationKey {
63 pub fn new(value: &Value) -> Self {
65 Self(normalise(value))
66 }
67
68 pub fn from_cell(cell: &crate::row::Cell) -> Self {
70 match cell.to_value() {
71 Some(value) => Self::new(&value),
72 None => Self(null()),
73 }
74 }
75
76 pub fn as_str(&self) -> &str {
78 &self.0
79 }
80}
81
82impl std::fmt::Display for RelationKey {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 f.write_str(&self.0)
85 }
86}
87
88fn normalise(value: &Value) -> String {
93 macro_rules! int {
94 ($v:expr) => {
95 $v.map(|n| (n as i128).to_string()).unwrap_or_else(null)
96 };
97 }
98
99 match value {
100 Value::TinyInt(v) => int!(*v),
101 Value::SmallInt(v) => int!(*v),
102 Value::Int(v) => int!(*v),
103 Value::BigInt(v) => int!(*v),
104 Value::TinyUnsigned(v) => int!(*v),
105 Value::SmallUnsigned(v) => int!(*v),
106 Value::Unsigned(v) => int!(*v),
107 Value::BigUnsigned(v) => int!(*v),
108 Value::Bool(v) => v.map(|b| b.to_string()).unwrap_or_else(null),
109 Value::String(v) => v.as_ref().map(|s| s.to_string()).unwrap_or_else(null),
110 Value::Char(v) => v.map(|c| c.to_string()).unwrap_or_else(null),
111 Value::Bytes(v) => v.as_ref().map(|b| hex(b)).unwrap_or_else(null),
112 other => format!("{other:?}"),
115 }
116}
117
118fn null() -> String {
119 "\0null".to_string()
120}
121
122fn hex(bytes: &[u8]) -> String {
123 bytes.iter().map(|byte| format!("{byte:02x}")).collect()
124}
125
126#[derive(Debug, Clone)]
131pub struct Related<C> {
132 near_key: String,
135 groups: HashMap<RelationKey, Vec<C>>,
136}
137
138impl<C> Related<C> {
139 pub fn empty(near_key: impl Into<String>) -> Self {
141 Self { near_key: near_key.into(), groups: HashMap::new() }
142 }
143
144 pub fn of<N: Model>(&self, near: &N) -> &[C] {
146 self.key_of(near).and_then(|key| self.groups.get(&key)).map_or(&[], Vec::as_slice)
147 }
148
149 pub fn one<N: Model>(&self, near: &N) -> Option<&C> {
152 self.of(near).first()
153 }
154
155 pub fn count_of<N: Model>(&self, near: &N) -> usize {
160 self.of(near).len()
161 }
162
163 pub fn is_empty(&self) -> bool {
165 self.groups.values().all(Vec::is_empty)
166 }
167
168 pub fn len(&self) -> usize {
170 self.groups.values().map(Vec::len).sum()
171 }
172
173 pub fn near_key(&self) -> &str {
175 &self.near_key
176 }
177
178 pub fn zip<N: Model>(mut self, near: Vec<N>) -> Vec<(N, Vec<C>)> {
183 near.into_iter()
184 .map(|model| {
185 let related = model
186 .value_of(&self.near_key)
187 .and_then(|value| self.groups.remove(&RelationKey::new(&value)))
188 .unwrap_or_default();
189 (model, related)
190 })
191 .collect()
192 }
193
194 fn key_of<N: Model>(&self, near: &N) -> Option<RelationKey> {
195 near.value_of(&self.near_key).map(|value| RelationKey::new(&value))
196 }
197
198 fn group_by(near_key: &str, far_key: &str, rows: Vec<C>) -> Self
200 where
201 C: Model,
202 {
203 let mut groups: HashMap<RelationKey, Vec<C>> = HashMap::new();
204 for row in rows {
205 let Some(value) = row.value_of(far_key) else { continue };
206 groups.entry(RelationKey::new(&value)).or_default().push(row);
207 }
208 Self { near_key: near_key.to_string(), groups }
209 }
210}
211
212#[derive(Debug, Clone, Default)]
214pub struct RelatedCounts {
215 near_key: String,
216 counts: HashMap<RelationKey, u64>,
217}
218
219impl RelatedCounts {
220 pub fn of<N: Model>(&self, near: &N) -> u64 {
223 near.value_of(&self.near_key)
224 .and_then(|value| self.counts.get(&RelationKey::new(&value)).copied())
225 .unwrap_or(0)
226 }
227
228 pub fn total(&self) -> u64 {
230 self.counts.values().sum()
231 }
232}
233
234#[async_trait::async_trait]
239pub trait Relation<P: Model>: Send + Sync {
240 type Related: Model;
242
243 fn near_key(&self) -> &str;
245
246 fn far_key(&self) -> &str;
248
249 async fn load(
251 &self,
252 parents: &[P],
253 related: &dyn Repository<Self::Related>,
254 ) -> Result<Related<Self::Related>>;
255
256 async fn count(
259 &self,
260 parents: &[P],
261 related: &dyn Repository<Self::Related>,
262 ) -> Result<RelatedCounts>;
263
264 async fn for_one(
270 &self,
271 parent: &P,
272 related: &dyn Repository<Self::Related>,
273 ) -> Result<Vec<Self::Related>>
274 where
275 Self: Sized,
276 {
277 let loaded = self.load(std::slice::from_ref(parent), related).await?;
278 Ok(loaded.of(parent).to_vec())
279 }
280}
281
282fn keys_of<P: Model>(parents: &[P], column: &str) -> Vec<Value> {
284 let mut seen = std::collections::HashSet::new();
285 let mut keys = Vec::with_capacity(parents.len());
286
287 for parent in parents {
288 let Some(value) = parent.value_of(column) else { continue };
289 if seen.insert(RelationKey::new(&value)) {
290 keys.push(value);
291 }
292 }
293 keys
294}
295
296fn conventional_foreign_key<M: Model>() -> String {
300 format!("{}_{}", str::snake(M::model_name()), M::primary_key())
301}
302
303pub struct HasMany<P, C> {
314 foreign_key: String,
315 local_key: String,
316 criteria: Criteria,
317 _sides: PhantomData<fn() -> (P, C)>,
318}
319
320impl<P: Model, C: Model> HasMany<P, C> {
321 pub fn new() -> Self {
323 Self {
324 foreign_key: conventional_foreign_key::<P>(),
325 local_key: P::primary_key().to_string(),
326 criteria: Criteria::new(),
327 _sides: PhantomData,
328 }
329 }
330
331 pub fn foreign_key(mut self, column: impl Into<String>) -> Self {
333 self.foreign_key = column.into();
334 self
335 }
336
337 pub fn local_key(mut self, column: impl Into<String>) -> Self {
339 self.local_key = column.into();
340 self
341 }
342
343 pub fn matching(mut self, criteria: Criteria) -> Self {
349 self.criteria = criteria;
350 self
351 }
352}
353
354impl<P: Model, C: Model> Default for HasMany<P, C> {
355 fn default() -> Self {
356 Self::new()
357 }
358}
359
360#[async_trait::async_trait]
361impl<P: Model, C: Model> Relation<P> for HasMany<P, C> {
362 type Related = C;
363
364 fn near_key(&self) -> &str {
365 &self.local_key
366 }
367
368 fn far_key(&self) -> &str {
369 &self.foreign_key
370 }
371
372 async fn load(&self, parents: &[P], related: &dyn Repository<C>) -> Result<Related<C>> {
373 let keys = keys_of(parents, &self.local_key);
374 if keys.is_empty() {
375 return Ok(Related::empty(&self.local_key));
376 }
377
378 let rows = related.matching(self.scoped(keys)).await?;
379 Ok(Related::group_by(&self.local_key, &self.foreign_key, rows))
380 }
381
382 async fn count(&self, parents: &[P], related: &dyn Repository<C>) -> Result<RelatedCounts> {
383 let keys = keys_of(parents, &self.local_key);
384 if keys.is_empty() {
385 return Ok(RelatedCounts::default());
386 }
387
388 let counts = related.count_grouped(&self.foreign_key, self.scoped(keys)).await?;
389 Ok(RelatedCounts { near_key: self.local_key.clone(), counts: counts.into_iter().collect() })
390 }
391}
392
393impl<P: Model, C: Model> HasMany<P, C> {
394 fn scoped(&self, keys: Vec<Value>) -> Criteria {
395 self.criteria.clone().where_in(&self.foreign_key, keys)
396 }
397}
398
399pub struct HasOne<P, C>(HasMany<P, C>);
410
411impl<P: Model, C: Model> HasOne<P, C> {
412 pub fn new() -> Self {
414 Self(HasMany::new())
415 }
416
417 pub fn foreign_key(mut self, column: impl Into<String>) -> Self {
419 self.0 = self.0.foreign_key(column);
420 self
421 }
422
423 pub fn local_key(mut self, column: impl Into<String>) -> Self {
425 self.0 = self.0.local_key(column);
426 self
427 }
428
429 pub fn matching(mut self, criteria: Criteria) -> Self {
431 self.0 = self.0.matching(criteria);
432 self
433 }
434}
435
436impl<P: Model, C: Model> Default for HasOne<P, C> {
437 fn default() -> Self {
438 Self::new()
439 }
440}
441
442#[async_trait::async_trait]
443impl<P: Model, C: Model> Relation<P> for HasOne<P, C> {
444 type Related = C;
445
446 fn near_key(&self) -> &str {
447 self.0.near_key()
448 }
449
450 fn far_key(&self) -> &str {
451 self.0.far_key()
452 }
453
454 async fn load(&self, parents: &[P], related: &dyn Repository<C>) -> Result<Related<C>> {
455 self.0.load(parents, related).await
456 }
457
458 async fn count(&self, parents: &[P], related: &dyn Repository<C>) -> Result<RelatedCounts> {
459 self.0.count(parents, related).await
460 }
461}
462
463pub struct BelongsTo<C, P> {
477 foreign_key: String,
478 owner_key: String,
479 criteria: Criteria,
480 _sides: PhantomData<fn() -> (C, P)>,
481}
482
483impl<C: Model, P: Model> BelongsTo<C, P> {
484 pub fn new() -> Self {
486 Self {
487 foreign_key: conventional_foreign_key::<P>(),
488 owner_key: P::primary_key().to_string(),
489 criteria: Criteria::new(),
490 _sides: PhantomData,
491 }
492 }
493
494 pub fn foreign_key(mut self, column: impl Into<String>) -> Self {
496 self.foreign_key = column.into();
497 self
498 }
499
500 pub fn owner_key(mut self, column: impl Into<String>) -> Self {
502 self.owner_key = column.into();
503 self
504 }
505
506 pub fn matching(mut self, criteria: Criteria) -> Self {
508 self.criteria = criteria;
509 self
510 }
511}
512
513impl<C: Model, P: Model> Default for BelongsTo<C, P> {
514 fn default() -> Self {
515 Self::new()
516 }
517}
518
519#[async_trait::async_trait]
520impl<C: Model, P: Model> Relation<C> for BelongsTo<C, P> {
521 type Related = P;
522
523 fn near_key(&self) -> &str {
524 &self.foreign_key
525 }
526
527 fn far_key(&self) -> &str {
528 &self.owner_key
529 }
530
531 async fn load(&self, children: &[C], related: &dyn Repository<P>) -> Result<Related<P>> {
532 let keys = keys_of(children, &self.foreign_key);
533 if keys.is_empty() {
534 return Ok(Related::empty(&self.foreign_key));
535 }
536
537 let criteria = self.criteria.clone().where_in(&self.owner_key, keys);
538 let rows = related.matching(criteria).await?;
539
540 Ok(Related::group_by(&self.foreign_key, &self.owner_key, rows))
541 }
542
543 async fn count(&self, children: &[C], related: &dyn Repository<P>) -> Result<RelatedCounts> {
544 let keys = keys_of(children, &self.foreign_key);
545 if keys.is_empty() {
546 return Ok(RelatedCounts::default());
547 }
548
549 let criteria = self.criteria.clone().where_in(&self.owner_key, keys);
550 let counts = related.count_grouped(&self.owner_key, criteria).await?;
551
552 Ok(RelatedCounts {
553 near_key: self.foreign_key.clone(),
554 counts: counts.into_iter().collect(),
555 })
556 }
557}
558
559pub struct BelongsToMany<P, C> {
579 pivot: String,
580 parent_pivot_key: String,
581 related_pivot_key: String,
582 parent_key: String,
583 related_key: String,
584 criteria: Criteria,
585 _sides: PhantomData<fn() -> (P, C)>,
586}
587
588impl<P: Model, C: Model> BelongsToMany<P, C> {
589 pub fn new(pivot: impl Into<String>) -> Self {
592 Self {
593 pivot: pivot.into(),
594 parent_pivot_key: conventional_foreign_key::<P>(),
595 related_pivot_key: conventional_foreign_key::<C>(),
596 parent_key: P::primary_key().to_string(),
597 related_key: C::primary_key().to_string(),
598 criteria: Criteria::new(),
599 _sides: PhantomData,
600 }
601 }
602
603 pub fn conventional_pivot() -> String {
606 let mut sides =
607 [str::snake(P::model_name()), str::snake(C::model_name())].map(|s| str::singular(&s));
608 sides.sort();
609 sides.join("_")
610 }
611
612 pub fn parent_pivot_key(mut self, column: impl Into<String>) -> Self {
614 self.parent_pivot_key = column.into();
615 self
616 }
617
618 pub fn related_pivot_key(mut self, column: impl Into<String>) -> Self {
620 self.related_pivot_key = column.into();
621 self
622 }
623
624 pub fn parent_key(mut self, column: impl Into<String>) -> Self {
626 self.parent_key = column.into();
627 self
628 }
629
630 pub fn related_key(mut self, column: impl Into<String>) -> Self {
632 self.related_key = column.into();
633 self
634 }
635
636 pub fn matching(mut self, criteria: Criteria) -> Self {
638 self.criteria = criteria;
639 self
640 }
641
642 pub fn pivot(&self) -> &str {
644 &self.pivot
645 }
646}
647
648#[async_trait::async_trait]
649impl<P: Model, C: Model> Relation<P> for BelongsToMany<P, C> {
650 type Related = C;
651
652 fn near_key(&self) -> &str {
653 &self.parent_key
654 }
655
656 fn far_key(&self) -> &str {
657 &self.related_key
658 }
659
660 async fn load(&self, parents: &[P], related: &dyn Repository<C>) -> Result<Related<C>> {
661 let keys = keys_of(parents, &self.parent_key);
662 if keys.is_empty() {
663 return Ok(Related::empty(&self.parent_key));
664 }
665
666 let links = related.pivot_links(self.links_for(keys)).await?;
667 if links.is_empty() {
668 return Ok(Related::empty(&self.parent_key));
669 }
670
671 let wanted: Vec<Value> = dedup(links.iter().map(|(_, related)| related.clone()));
673 let criteria = self.criteria.clone().where_in(&self.related_key, wanted);
674 let rows = related.matching(criteria).await?;
675
676 let by_key: HashMap<RelationKey, C> = rows
679 .into_iter()
680 .filter_map(|row| {
681 row.value_of(&self.related_key).map(|key| (RelationKey::new(&key), row))
682 })
683 .collect();
684
685 let mut groups: HashMap<RelationKey, Vec<C>> = HashMap::new();
686 for (parent, related) in &links {
687 if let Some(row) = by_key.get(&RelationKey::new(related)) {
688 groups.entry(RelationKey::new(parent)).or_default().push(row.clone());
689 }
690 }
691
692 Ok(Related { near_key: self.parent_key.clone(), groups })
693 }
694
695 async fn count(&self, parents: &[P], related: &dyn Repository<C>) -> Result<RelatedCounts> {
696 let keys = keys_of(parents, &self.parent_key);
697 if keys.is_empty() {
698 return Ok(RelatedCounts::default());
699 }
700
701 let links = related.pivot_links(self.links_for(keys)).await?;
705
706 let mut counts: HashMap<RelationKey, u64> = HashMap::new();
707 for (parent, _) in &links {
708 *counts.entry(RelationKey::new(parent)).or_insert(0) += 1;
709 }
710
711 Ok(RelatedCounts { near_key: self.parent_key.clone(), counts })
712 }
713}
714
715impl<P: Model, C: Model> BelongsToMany<P, C> {
716 fn links_for(&self, keys: Vec<Value>) -> PivotQuery {
717 PivotQuery {
718 table: self.pivot.clone(),
719 parent_column: self.parent_pivot_key.clone(),
720 related_column: self.related_pivot_key.clone(),
721 parent_keys: keys,
722 }
723 }
724}
725
726#[derive(Debug, Clone)]
731pub struct PivotQuery {
732 pub table: String,
734 pub parent_column: String,
736 pub related_column: String,
738 pub parent_keys: Vec<Value>,
740}
741
742fn dedup(values: impl Iterator<Item = Value>) -> Vec<Value> {
743 let mut seen = std::collections::HashSet::new();
744 let mut unique = Vec::new();
745 for value in values {
746 if seen.insert(RelationKey::new(&value)) {
747 unique.push(value);
748 }
749 }
750 unique
751}
752
753#[cfg(test)]
754mod tests {
755 use super::*;
756 use crate::testing::{fake_database, MemoryConnection};
757 use crate::{Dialect, EntityRepository};
758 use rainier_orm::Entity;
759
760 #[derive(Debug, Clone, PartialEq, Entity)]
761 #[orm(table = "users")]
762 struct User {
763 #[orm(pk, auto_increment)]
764 id: u64,
765 name: String,
766 }
767
768 impl Model for User {}
769
770 #[derive(Debug, Clone, PartialEq, Entity)]
771 #[orm(table = "posts")]
772 struct Post {
773 #[orm(pk, auto_increment)]
774 id: u64,
775 title: String,
776 user_id: u64,
777 }
778
779 impl Model for Post {}
780
781 #[derive(Debug, Clone, PartialEq, Entity)]
782 #[orm(table = "tags")]
783 struct Tag {
784 #[orm(pk, auto_increment)]
785 id: u64,
786 name: String,
787 }
788
789 impl Model for Tag {}
790
791 fn user(id: u64) -> User {
792 User { id, name: format!("user-{id}") }
793 }
794
795 fn post(id: u64, user_id: u64) -> Post {
796 Post { id, title: format!("post-{id}"), user_id }
797 }
798
799 #[test]
800 fn the_conventional_keys_come_from_the_model_names() {
801 let has_many = HasMany::<User, Post>::new();
802 assert_eq!(has_many.far_key(), "user_id");
803 assert_eq!(has_many.near_key(), "id");
804
805 let belongs_to = BelongsTo::<Post, User>::new();
806 assert_eq!(belongs_to.near_key(), "user_id", "the column on the post");
807 assert_eq!(belongs_to.far_key(), "id", "the column on the user");
808
809 assert_eq!(BelongsToMany::<Post, Tag>::conventional_pivot(), "post_tag");
810 }
811
812 #[test]
813 fn keys_are_normalised_across_integer_widths() {
814 assert_eq!(
817 RelationKey::new(&Value::Int(Some(7))),
818 RelationKey::new(&Value::BigUnsigned(Some(7)))
819 );
820 assert_ne!(RelationKey::new(&Value::Int(Some(7))), RelationKey::new(&Value::Int(Some(8))));
821
822 let null_key = RelationKey::new(&Value::BigUnsigned(None));
824 assert_ne!(null_key, RelationKey::new(&Value::BigUnsigned(Some(0))));
825 }
826
827 #[test]
828 fn related_rows_are_grouped_by_the_foreign_key() {
829 let related = Related::group_by("id", "user_id", vec![post(1, 7), post(2, 7), post(3, 8)]);
830
831 assert_eq!(related.of(&user(7)).len(), 2);
832 assert_eq!(related.of(&user(8)).len(), 1);
833 assert_eq!(related.of(&user(9)), &[], "a parent with no children");
834 assert_eq!(related.one(&user(8)).unwrap().id, 3);
835 assert_eq!(related.len(), 3);
836 }
837
838 #[test]
839 fn zipping_pairs_each_parent_with_its_own() {
840 let related = Related::group_by("id", "user_id", vec![post(1, 7), post(2, 8), post(3, 7)]);
841 let pairs = related.zip(vec![user(7), user(8), user(9)]);
842
843 assert_eq!(pairs[0].1.len(), 2);
844 assert_eq!(pairs[1].1.len(), 1);
845 assert!(pairs[2].1.is_empty(), "still present, with nothing");
846 }
847
848 #[test]
849 fn duplicate_parent_keys_are_asked_for_once() {
850 let posts = vec![post(1, 7), post(2, 7), post(3, 8)];
853 assert_eq!(keys_of(&posts, "user_id").len(), 2);
854 }
855
856 #[tokio::test]
857 async fn loading_many_parents_is_one_query() {
858 let (db, connection) = fake_database(MemoryConnection::new(Dialect::Sqlite));
859 let posts = EntityRepository::<Post>::new(db);
860
861 let users: Vec<User> = (1..=50).map(user).collect();
862 HasMany::<User, Post>::new().load(&users, &posts).await.unwrap();
863
864 assert_eq!(connection.statement_count(), 1, "fifty parents, one query");
865 let sql = connection.last_statement().unwrap();
866 assert!(sql.contains("IN"), "{sql}");
867 }
868
869 #[tokio::test]
870 async fn loading_no_parents_asks_nothing() {
871 let (db, connection) = fake_database(MemoryConnection::new(Dialect::Sqlite));
872 let posts = EntityRepository::<Post>::new(db);
873
874 let loaded = HasMany::<User, Post>::new().load(&[], &posts).await.unwrap();
875
876 assert_eq!(connection.statement_count(), 0, "an empty IN () is not a query worth running");
877 assert!(loaded.is_empty());
878 }
879
880 #[tokio::test]
881 async fn a_constrained_load_keeps_its_filters() {
882 let (db, connection) = fake_database(MemoryConnection::new(Dialect::Sqlite));
883 let posts = EntityRepository::<Post>::new(db);
884
885 HasMany::<User, Post>::new()
886 .matching(Criteria::new().order_by_desc("id"))
887 .load(&[user(1)], &posts)
888 .await
889 .unwrap();
890
891 let sql = connection.last_statement().unwrap();
892 assert!(sql.contains("ORDER BY"), "{sql}");
893 }
894
895 #[tokio::test]
896 async fn belongs_to_many_reads_the_pivot_then_the_rows() {
897 let (db, connection) = fake_database(MemoryConnection::new(Dialect::Sqlite));
898 let tags = EntityRepository::<Tag>::new(db);
899
900 BelongsToMany::<Post, Tag>::new("post_tag")
901 .load(&[post(1, 7), post(2, 7)], &tags)
902 .await
903 .unwrap();
904
905 assert_eq!(connection.statement_count(), 1);
908 assert!(connection.last_statement().unwrap().contains("post_tag"));
909 }
910}