1use crate::{Error, Model, Value};
9use asupersync::{Cx, Outcome};
10use serde::{Deserialize, Deserializer, Serialize, Serializer};
11use std::fmt;
12use std::future::Future;
13use std::sync::OnceLock;
14
15#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
17pub enum RelationshipKind {
18 OneToOne,
20 #[default]
22 ManyToOne,
23 OneToMany,
25 ManyToMany,
27}
28
29#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
34pub enum PassiveDeletes {
35 #[default]
37 Active,
38 Passive,
41 All,
44}
45
46#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
51pub enum LazyLoadStrategy {
52 #[default]
54 Select,
55 Joined,
57 Subquery,
59 Selectin,
61 Dynamic,
63 NoLoad,
65 RaiseOnSql,
67 WriteOnly,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub struct LinkTableInfo {
74 pub table_name: &'static str,
76
77 pub local_column: &'static str,
79
80 pub remote_column: &'static str,
82}
83
84impl LinkTableInfo {
85 #[must_use]
87 pub const fn new(
88 table_name: &'static str,
89 local_column: &'static str,
90 remote_column: &'static str,
91 ) -> Self {
92 Self {
93 table_name,
94 local_column,
95 remote_column,
96 }
97 }
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102pub struct RelationshipInfo {
103 pub name: &'static str,
105
106 pub related_table: &'static str,
108
109 pub kind: RelationshipKind,
111
112 pub local_key: Option<&'static str>,
115
116 pub remote_key: Option<&'static str>,
119
120 pub link_table: Option<LinkTableInfo>,
122
123 pub back_populates: Option<&'static str>,
125
126 pub lazy: bool,
128
129 pub cascade_delete: bool,
131
132 pub passive_deletes: PassiveDeletes,
134
135 pub order_by: Option<&'static str>,
137
138 pub lazy_strategy: Option<LazyLoadStrategy>,
140
141 pub cascade: Option<&'static str>,
143
144 pub uselist: Option<bool>,
149}
150
151impl RelationshipInfo {
152 #[must_use]
154 pub const fn new(
155 name: &'static str,
156 related_table: &'static str,
157 kind: RelationshipKind,
158 ) -> Self {
159 Self {
160 name,
161 related_table,
162 kind,
163 local_key: None,
164 remote_key: None,
165 link_table: None,
166 back_populates: None,
167 lazy: false,
168 cascade_delete: false,
169 passive_deletes: PassiveDeletes::Active,
170 order_by: None,
171 lazy_strategy: None,
172 cascade: None,
173 uselist: None,
174 }
175 }
176
177 #[must_use]
179 pub const fn local_key(mut self, key: &'static str) -> Self {
180 self.local_key = Some(key);
181 self
182 }
183
184 #[must_use]
186 pub const fn remote_key(mut self, key: &'static str) -> Self {
187 self.remote_key = Some(key);
188 self
189 }
190
191 #[must_use]
193 pub const fn link_table(mut self, info: LinkTableInfo) -> Self {
194 self.link_table = Some(info);
195 self
196 }
197
198 #[must_use]
200 pub const fn back_populates(mut self, field: &'static str) -> Self {
201 self.back_populates = Some(field);
202 self
203 }
204
205 #[must_use]
207 pub const fn lazy(mut self, value: bool) -> Self {
208 self.lazy = value;
209 self
210 }
211
212 #[must_use]
214 pub const fn cascade_delete(mut self, value: bool) -> Self {
215 self.cascade_delete = value;
216 self
217 }
218
219 #[must_use]
225 pub const fn passive_deletes(mut self, value: PassiveDeletes) -> Self {
226 self.passive_deletes = value;
227 self
228 }
229
230 #[must_use]
232 pub const fn order_by(mut self, ordering: &'static str) -> Self {
233 self.order_by = Some(ordering);
234 self
235 }
236
237 #[must_use]
239 pub const fn order_by_opt(mut self, ordering: Option<&'static str>) -> Self {
240 self.order_by = ordering;
241 self
242 }
243
244 #[must_use]
246 pub const fn lazy_strategy(mut self, strategy: LazyLoadStrategy) -> Self {
247 self.lazy_strategy = Some(strategy);
248 self
249 }
250
251 #[must_use]
253 pub const fn lazy_strategy_opt(mut self, strategy: Option<LazyLoadStrategy>) -> Self {
254 self.lazy_strategy = strategy;
255 self
256 }
257
258 #[must_use]
260 pub const fn cascade(mut self, opts: &'static str) -> Self {
261 self.cascade = Some(opts);
262 self
263 }
264
265 #[must_use]
267 pub const fn cascade_opt(mut self, opts: Option<&'static str>) -> Self {
268 self.cascade = opts;
269 self
270 }
271
272 #[must_use]
274 pub const fn uselist(mut self, value: bool) -> Self {
275 self.uselist = Some(value);
276 self
277 }
278
279 #[must_use]
281 pub const fn uselist_opt(mut self, value: Option<bool>) -> Self {
282 self.uselist = value;
283 self
284 }
285
286 #[must_use]
288 pub const fn is_passive_deletes(&self) -> bool {
289 matches!(
290 self.passive_deletes,
291 PassiveDeletes::Passive | PassiveDeletes::All
292 )
293 }
294
295 #[must_use]
297 pub const fn is_passive_deletes_all(&self) -> bool {
298 matches!(self.passive_deletes, PassiveDeletes::All)
299 }
300}
301
302impl Default for RelationshipInfo {
303 fn default() -> Self {
304 Self::new("", "", RelationshipKind::default())
305 }
306}
307
308pub fn find_relationship<M: crate::Model>(field_name: &str) -> Option<&'static RelationshipInfo> {
321 M::RELATIONSHIPS.iter().find(|r| r.name == field_name)
322}
323
324pub fn find_back_relationship(
334 source_rel: &RelationshipInfo,
335 target_relationships: &'static [RelationshipInfo],
336) -> Option<&'static RelationshipInfo> {
337 let back_field = source_rel.back_populates?;
338 target_relationships.iter().find(|r| r.name == back_field)
339}
340
341pub fn validate_back_populates<Source: crate::Model, Target: crate::Model>(
348 source_field: &str,
349) -> Result<(), String> {
350 let source_rel = find_relationship::<Source>(source_field).ok_or_else(|| {
351 format!(
352 "No relationship '{}' on {}",
353 source_field,
354 Source::TABLE_NAME
355 )
356 })?;
357
358 let Some(back_field) = source_rel.back_populates else {
359 return Ok(());
361 };
362
363 let target_rel = find_relationship::<Target>(back_field).ok_or_else(|| {
364 format!(
365 "{}.{} has back_populates='{}' but {}.{} does not exist",
366 Source::TABLE_NAME,
367 source_field,
368 back_field,
369 Target::TABLE_NAME,
370 back_field
371 )
372 })?;
373
374 if let Some(target_back) = target_rel.back_populates {
376 if target_back != source_field {
377 return Err(format!(
378 "{}.{} has back_populates='{}' but {}.{} has back_populates='{}' (expected '{}')",
379 Source::TABLE_NAME,
380 source_field,
381 back_field,
382 Target::TABLE_NAME,
383 back_field,
384 target_back,
385 source_field
386 ));
387 }
388 }
389
390 Ok(())
391}
392
393pub trait LazyLoader<M: Model> {
399 fn get(&mut self, cx: &Cx, pk: Value)
401 -> impl Future<Output = Outcome<Option<M>, Error>> + Send;
402}
403
404pub struct Related<T: Model> {
411 fk_value: Option<Value>,
412 loaded: OnceLock<Option<T>>,
413}
414
415impl<T: Model> Related<T> {
416 #[must_use]
418 pub const fn empty() -> Self {
419 Self {
420 fk_value: None,
421 loaded: OnceLock::new(),
422 }
423 }
424
425 #[must_use]
427 pub fn from_fk(fk: impl Into<Value>) -> Self {
428 Self {
429 fk_value: Some(fk.into()),
430 loaded: OnceLock::new(),
431 }
432 }
433
434 #[must_use]
436 pub fn loaded(obj: T) -> Self {
437 let cell = OnceLock::new();
438 let _ = cell.set(Some(obj));
439 Self {
440 fk_value: None,
441 loaded: cell,
442 }
443 }
444
445 #[must_use]
447 pub fn get(&self) -> Option<&T> {
448 self.loaded.get().and_then(|o| o.as_ref())
449 }
450
451 #[must_use]
453 pub fn is_loaded(&self) -> bool {
454 self.loaded.get().is_some()
455 }
456
457 #[must_use]
459 pub fn is_empty(&self) -> bool {
460 self.fk_value.is_none()
461 }
462
463 #[must_use]
465 pub fn fk(&self) -> Option<&Value> {
466 self.fk_value.as_ref()
467 }
468
469 pub fn set_loaded(&self, obj: Option<T>) -> Result<(), Option<T>> {
471 self.loaded.set(obj)
472 }
473}
474
475impl<T: Model> Default for Related<T> {
476 fn default() -> Self {
477 Self::empty()
478 }
479}
480
481impl<T: Model + Clone> Clone for Related<T> {
482 fn clone(&self) -> Self {
483 let cloned = Self {
484 fk_value: self.fk_value.clone(),
485 loaded: OnceLock::new(),
486 };
487
488 if let Some(value) = self.loaded.get() {
489 let _ = cloned.loaded.set(value.clone());
490 }
491
492 cloned
493 }
494}
495
496impl<T: Model + fmt::Debug> fmt::Debug for Related<T> {
497 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
498 let state = if self.is_loaded() {
499 "loaded"
500 } else if self.is_empty() {
501 "empty"
502 } else {
503 "unloaded"
504 };
505
506 f.debug_struct("Related")
507 .field("state", &state)
508 .field("fk_value", &self.fk_value)
509 .field("loaded", &self.get())
510 .finish()
511 }
512}
513
514impl<T> Serialize for Related<T>
515where
516 T: Model + Serialize,
517{
518 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
519 match self.loaded.get() {
520 Some(Some(obj)) => obj.serialize(serializer),
521 Some(None) | None => serializer.serialize_none(),
522 }
523 }
524}
525
526impl<'de, T> Deserialize<'de> for Related<T>
527where
528 T: Model + Deserialize<'de>,
529{
530 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
531 let opt = Option::<T>::deserialize(deserializer)?;
532 Ok(match opt {
533 Some(obj) => Self::loaded(obj),
534 None => Self::empty(),
535 })
536 }
537}
538
539pub struct RelatedMany<T: Model> {
548 loaded: OnceLock<Vec<T>>,
550 fk_column: &'static str,
552 parent_pk: Option<Value>,
554 link_table: Option<LinkTableInfo>,
556 pending_links: std::sync::Mutex<Vec<Vec<Value>>>,
558 pending_unlinks: std::sync::Mutex<Vec<Vec<Value>>>,
560}
561
562impl<T: Model> RelatedMany<T> {
563 #[must_use]
568 pub fn new(fk_column: &'static str) -> Self {
569 Self {
570 loaded: OnceLock::new(),
571 fk_column,
572 parent_pk: None,
573 link_table: None,
574 pending_links: std::sync::Mutex::new(Vec::new()),
575 pending_unlinks: std::sync::Mutex::new(Vec::new()),
576 }
577 }
578
579 #[must_use]
588 pub fn with_link_table(link_table: LinkTableInfo) -> Self {
589 Self {
590 loaded: OnceLock::new(),
591 fk_column: "",
592 parent_pk: None,
593 link_table: Some(link_table),
594 pending_links: std::sync::Mutex::new(Vec::new()),
595 pending_unlinks: std::sync::Mutex::new(Vec::new()),
596 }
597 }
598
599 #[must_use]
601 pub fn with_parent_pk(fk_column: &'static str, pk: impl Into<Value>) -> Self {
602 Self {
603 loaded: OnceLock::new(),
604 fk_column,
605 parent_pk: Some(pk.into()),
606 link_table: None,
607 pending_links: std::sync::Mutex::new(Vec::new()),
608 pending_unlinks: std::sync::Mutex::new(Vec::new()),
609 }
610 }
611
612 #[must_use]
614 pub fn is_loaded(&self) -> bool {
615 self.loaded.get().is_some()
616 }
617
618 #[must_use]
620 pub fn get(&self) -> Option<&[T]> {
621 self.loaded.get().map(Vec::as_slice)
622 }
623
624 #[must_use]
626 pub fn len(&self) -> usize {
627 self.loaded.get().map_or(0, Vec::len)
628 }
629
630 #[must_use]
632 pub fn is_empty(&self) -> bool {
633 self.loaded.get().is_none_or(Vec::is_empty)
634 }
635
636 pub fn set_loaded(&self, objects: Vec<T>) -> Result<(), Vec<T>> {
638 self.loaded.set(objects)
639 }
640
641 pub fn iter(&self) -> impl Iterator<Item = &T> {
643 self.loaded.get().map_or([].iter(), |v| v.iter())
644 }
645
646 #[must_use]
648 pub fn fk_column(&self) -> &'static str {
649 self.fk_column
650 }
651
652 #[must_use]
654 pub fn parent_pk(&self) -> Option<&Value> {
655 self.parent_pk.as_ref()
656 }
657
658 pub fn set_parent_pk(&mut self, pk: impl Into<Value>) {
660 self.parent_pk = Some(pk.into());
661 }
662
663 #[must_use]
665 pub fn link_table(&self) -> Option<&LinkTableInfo> {
666 self.link_table.as_ref()
667 }
668
669 #[must_use]
671 pub fn is_many_to_many(&self) -> bool {
672 self.link_table.is_some()
673 }
674
675 pub fn link(&self, obj: &T) {
689 let pk = obj.primary_key_value();
690 match self.pending_links.lock() {
691 Ok(mut pending) => {
692 if !pending.contains(&pk) {
694 pending.push(pk);
695 }
696 }
697 Err(poisoned) => {
698 let mut pending = poisoned.into_inner();
701 if !pending.contains(&pk) {
702 pending.push(pk);
703 }
704 }
705 }
706 }
707
708 pub fn unlink(&self, obj: &T) {
722 let pk = obj.primary_key_value();
723 match self.pending_unlinks.lock() {
724 Ok(mut pending) => {
725 if !pending.contains(&pk) {
727 pending.push(pk);
728 }
729 }
730 Err(poisoned) => {
731 let mut pending = poisoned.into_inner();
734 if !pending.contains(&pk) {
735 pending.push(pk);
736 }
737 }
738 }
739 }
740
741 pub fn take_pending_links(&self) -> Vec<Vec<Value>> {
746 match self.pending_links.lock() {
747 Ok(mut v) => std::mem::take(&mut *v),
748 Err(poisoned) => {
749 std::mem::take(&mut *poisoned.into_inner())
751 }
752 }
753 }
754
755 pub fn take_pending_unlinks(&self) -> Vec<Vec<Value>> {
760 match self.pending_unlinks.lock() {
761 Ok(mut v) => std::mem::take(&mut *v),
762 Err(poisoned) => {
763 std::mem::take(&mut *poisoned.into_inner())
765 }
766 }
767 }
768
769 #[must_use]
771 pub fn has_pending_ops(&self) -> bool {
772 let has_links = match self.pending_links.lock() {
773 Ok(v) => !v.is_empty(),
774 Err(poisoned) => !poisoned.into_inner().is_empty(),
775 };
776 let has_unlinks = match self.pending_unlinks.lock() {
777 Ok(v) => !v.is_empty(),
778 Err(poisoned) => !poisoned.into_inner().is_empty(),
779 };
780 has_links || has_unlinks
781 }
782}
783
784impl<T: Model> Default for RelatedMany<T> {
785 fn default() -> Self {
786 Self::new("")
787 }
788}
789
790impl<T: Model + Clone> Clone for RelatedMany<T> {
791 fn clone(&self) -> Self {
792 let cloned_links = match self.pending_links.lock() {
794 Ok(v) => v.clone(),
795 Err(poisoned) => poisoned.into_inner().clone(),
796 };
797
798 let cloned_unlinks = match self.pending_unlinks.lock() {
800 Ok(v) => v.clone(),
801 Err(poisoned) => poisoned.into_inner().clone(),
802 };
803
804 let cloned = Self {
805 loaded: OnceLock::new(),
806 fk_column: self.fk_column,
807 parent_pk: self.parent_pk.clone(),
808 link_table: self.link_table,
809 pending_links: std::sync::Mutex::new(cloned_links),
810 pending_unlinks: std::sync::Mutex::new(cloned_unlinks),
811 };
812
813 if let Some(vec) = self.loaded.get() {
814 let _ = cloned.loaded.set(vec.clone());
815 }
816
817 cloned
818 }
819}
820
821impl<T: Model + fmt::Debug> fmt::Debug for RelatedMany<T> {
822 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
823 let pending_links_count = self.pending_links.lock().map_or(0, |v| v.len());
824 let pending_unlinks_count = self.pending_unlinks.lock().map_or(0, |v| v.len());
825
826 f.debug_struct("RelatedMany")
827 .field("loaded", &self.loaded.get())
828 .field("fk_column", &self.fk_column)
829 .field("parent_pk", &self.parent_pk)
830 .field("link_table", &self.link_table)
831 .field("pending_links_count", &pending_links_count)
832 .field("pending_unlinks_count", &pending_unlinks_count)
833 .finish()
834 }
835}
836
837impl<T> Serialize for RelatedMany<T>
838where
839 T: Model + Serialize,
840{
841 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
842 match self.loaded.get() {
843 Some(vec) => vec.serialize(serializer),
844 None => Vec::<T>::new().serialize(serializer),
845 }
846 }
847}
848
849impl<'de, T> Deserialize<'de> for RelatedMany<T>
850where
851 T: Model + Deserialize<'de>,
852{
853 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
854 let vec = Vec::<T>::deserialize(deserializer)?;
855 let rel = Self::new("");
856 let _ = rel.loaded.set(vec);
857 Ok(rel)
858 }
859}
860
861impl<'a, T: Model> IntoIterator for &'a RelatedMany<T> {
862 type Item = &'a T;
863 type IntoIter = std::slice::Iter<'a, T>;
864
865 fn into_iter(self) -> Self::IntoIter {
866 self.loaded.get().map_or([].iter(), |v| v.iter())
867 }
868}
869
870pub struct Lazy<T: Model> {
911 fk_value: Option<Value>,
913 loaded: OnceLock<Option<T>>,
915 load_attempted: std::sync::atomic::AtomicBool,
917}
918
919impl<T: Model> Lazy<T> {
920 #[must_use]
922 pub fn empty() -> Self {
923 Self {
924 fk_value: None,
925 loaded: OnceLock::new(),
926 load_attempted: std::sync::atomic::AtomicBool::new(false),
927 }
928 }
929
930 #[must_use]
932 pub fn from_fk(fk: impl Into<Value>) -> Self {
933 Self {
934 fk_value: Some(fk.into()),
935 loaded: OnceLock::new(),
936 load_attempted: std::sync::atomic::AtomicBool::new(false),
937 }
938 }
939
940 #[must_use]
942 pub fn loaded(obj: T) -> Self {
943 let cell = OnceLock::new();
944 let _ = cell.set(Some(obj));
945 Self {
946 fk_value: None,
947 loaded: cell,
948 load_attempted: std::sync::atomic::AtomicBool::new(true),
949 }
950 }
951
952 pub async fn load<L>(&mut self, cx: &Cx, loader: &mut L) -> Outcome<Option<&T>, Error>
958 where
959 L: LazyLoader<T> + ?Sized,
960 {
961 if self.is_loaded() {
962 return Outcome::Ok(self.get());
963 }
964
965 let Some(fk) = self.fk_value.clone() else {
966 let _ = self.set_loaded(None);
967 return Outcome::Ok(None);
968 };
969
970 match loader.get(cx, fk).await {
971 Outcome::Ok(obj) => {
972 let _ = self.set_loaded(obj);
973 Outcome::Ok(self.get())
974 }
975 Outcome::Err(e) => Outcome::Err(e),
976 Outcome::Cancelled(r) => Outcome::Cancelled(r),
977 Outcome::Panicked(p) => Outcome::Panicked(p),
978 }
979 }
980
981 #[must_use]
983 pub fn get(&self) -> Option<&T> {
984 self.loaded.get().and_then(|o| o.as_ref())
985 }
986
987 #[must_use]
989 pub fn is_loaded(&self) -> bool {
990 self.load_attempted
991 .load(std::sync::atomic::Ordering::Acquire)
992 }
993
994 #[must_use]
996 pub fn is_empty(&self) -> bool {
997 self.fk_value.is_none()
998 }
999
1000 #[must_use]
1002 pub fn fk(&self) -> Option<&Value> {
1003 self.fk_value.as_ref()
1004 }
1005
1006 pub fn set_loaded(&self, obj: Option<T>) -> Result<(), Option<T>> {
1010 match self.loaded.set(obj) {
1011 Ok(()) => {
1012 self.load_attempted
1013 .store(true, std::sync::atomic::Ordering::Release);
1014 Ok(())
1015 }
1016 Err(v) => Err(v),
1017 }
1018 }
1019
1020 pub fn reset(&mut self) {
1024 self.loaded = OnceLock::new();
1025 self.load_attempted = std::sync::atomic::AtomicBool::new(false);
1026 }
1027}
1028
1029impl<T: Model> Default for Lazy<T> {
1030 fn default() -> Self {
1031 Self::empty()
1032 }
1033}
1034
1035impl<T: Model + Clone> Clone for Lazy<T> {
1036 fn clone(&self) -> Self {
1037 let cloned = Self {
1038 fk_value: self.fk_value.clone(),
1039 loaded: OnceLock::new(),
1040 load_attempted: std::sync::atomic::AtomicBool::new(
1041 self.load_attempted
1042 .load(std::sync::atomic::Ordering::Acquire),
1043 ),
1044 };
1045
1046 if let Some(value) = self.loaded.get() {
1047 let _ = cloned.loaded.set(value.clone());
1048 }
1049
1050 cloned
1051 }
1052}
1053
1054impl<T: Model + fmt::Debug> fmt::Debug for Lazy<T> {
1055 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1056 let state = if self.is_loaded() {
1057 "loaded"
1058 } else if self.is_empty() {
1059 "empty"
1060 } else {
1061 "unloaded"
1062 };
1063
1064 f.debug_struct("Lazy")
1065 .field("state", &state)
1066 .field("fk_value", &self.fk_value)
1067 .field("loaded", &self.get())
1068 .field(
1069 "load_attempted",
1070 &self
1071 .load_attempted
1072 .load(std::sync::atomic::Ordering::Acquire),
1073 )
1074 .finish()
1075 }
1076}
1077
1078impl<T> Serialize for Lazy<T>
1079where
1080 T: Model + Serialize,
1081{
1082 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1083 match self.loaded.get() {
1084 Some(Some(obj)) => obj.serialize(serializer),
1085 Some(None) | None => serializer.serialize_none(),
1086 }
1087 }
1088}
1089
1090impl<'de, T> Deserialize<'de> for Lazy<T>
1091where
1092 T: Model + Deserialize<'de>,
1093{
1094 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1095 let opt = Option::<T>::deserialize(deserializer)?;
1096 Ok(match opt {
1097 Some(obj) => Self::loaded(obj),
1098 None => Self::empty(),
1099 })
1100 }
1101}
1102
1103#[cfg(test)]
1104mod tests {
1105 use super::*;
1106 use crate::{FieldInfo, Result, Row};
1107 use asupersync::runtime::RuntimeBuilder;
1108 use serde::{Deserialize, Serialize};
1109
1110 #[test]
1111 fn test_relationship_kind_default() {
1112 assert_eq!(RelationshipKind::default(), RelationshipKind::ManyToOne);
1113 }
1114
1115 #[test]
1116 fn test_relationship_info_builder_chain() {
1117 let info = RelationshipInfo::new("team", "teams", RelationshipKind::ManyToOne)
1118 .local_key("team_id")
1119 .back_populates("heroes")
1120 .lazy(true)
1121 .cascade_delete(true)
1122 .passive_deletes(PassiveDeletes::Passive);
1123
1124 assert_eq!(info.name, "team");
1125 assert_eq!(info.related_table, "teams");
1126 assert_eq!(info.kind, RelationshipKind::ManyToOne);
1127 assert_eq!(info.local_key, Some("team_id"));
1128 assert_eq!(info.remote_key, None);
1129 assert_eq!(info.link_table, None);
1130 assert_eq!(info.back_populates, Some("heroes"));
1131 assert!(info.lazy);
1132 assert!(info.cascade_delete);
1133 assert_eq!(info.passive_deletes, PassiveDeletes::Passive);
1134 }
1135
1136 #[test]
1137 fn test_passive_deletes_default() {
1138 assert_eq!(PassiveDeletes::default(), PassiveDeletes::Active);
1139 }
1140
1141 #[test]
1142 fn test_passive_deletes_helper_methods() {
1143 let active_info = RelationshipInfo::new("test", "test", RelationshipKind::OneToMany)
1145 .passive_deletes(PassiveDeletes::Active);
1146 assert!(!active_info.is_passive_deletes());
1147 assert!(!active_info.is_passive_deletes_all());
1148
1149 let passive_info = RelationshipInfo::new("test", "test", RelationshipKind::OneToMany)
1151 .passive_deletes(PassiveDeletes::Passive);
1152 assert!(passive_info.is_passive_deletes());
1153 assert!(!passive_info.is_passive_deletes_all());
1154
1155 let all_info = RelationshipInfo::new("test", "test", RelationshipKind::OneToMany)
1157 .passive_deletes(PassiveDeletes::All);
1158 assert!(all_info.is_passive_deletes());
1159 assert!(all_info.is_passive_deletes_all());
1160 }
1161
1162 #[test]
1163 fn test_relationship_info_new_has_active_passive_deletes() {
1164 let info = RelationshipInfo::new("test", "test", RelationshipKind::ManyToOne);
1166 assert_eq!(info.passive_deletes, PassiveDeletes::Active);
1167 assert!(!info.is_passive_deletes());
1168 }
1169
1170 #[test]
1171 fn test_link_table_info_new() {
1172 let link = LinkTableInfo::new("hero_powers", "hero_id", "power_id");
1173 assert_eq!(link.table_name, "hero_powers");
1174 assert_eq!(link.local_column, "hero_id");
1175 assert_eq!(link.remote_column, "power_id");
1176 }
1177
1178 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1179 struct Team {
1180 id: Option<i64>,
1181 name: String,
1182 }
1183
1184 impl Model for Team {
1185 const TABLE_NAME: &'static str = "teams";
1186 const PRIMARY_KEY: &'static [&'static str] = &["id"];
1187
1188 fn fields() -> &'static [FieldInfo] {
1189 &[]
1190 }
1191
1192 fn to_row(&self) -> Vec<(&'static str, Value)> {
1193 vec![]
1194 }
1195
1196 fn from_row(_row: &Row) -> Result<Self> {
1197 Ok(Self {
1198 id: None,
1199 name: String::new(),
1200 })
1201 }
1202
1203 fn primary_key_value(&self) -> Vec<Value> {
1204 match self.id {
1205 Some(id) => vec![Value::from(id)],
1206 None => vec![],
1207 }
1208 }
1209
1210 fn is_new(&self) -> bool {
1211 self.id.is_none()
1212 }
1213 }
1214
1215 #[test]
1216 fn test_related_empty_creates_unloaded_state() {
1217 let rel = Related::<Team>::empty();
1218 assert!(rel.is_empty());
1219 assert!(!rel.is_loaded());
1220 assert!(rel.get().is_none());
1221 assert!(rel.fk().is_none());
1222 }
1223
1224 #[test]
1225 fn test_related_from_fk_stores_value() {
1226 let rel = Related::<Team>::from_fk(42_i64);
1227 assert!(!rel.is_empty());
1228 assert_eq!(rel.fk(), Some(&Value::from(42_i64)));
1229 assert!(!rel.is_loaded());
1230 assert!(rel.get().is_none());
1231 }
1232
1233 #[test]
1234 fn test_related_loaded_sets_object() {
1235 let team = Team {
1236 id: Some(1),
1237 name: "Avengers".to_string(),
1238 };
1239 let rel = Related::loaded(team.clone());
1240 assert!(rel.is_loaded());
1241 assert!(rel.fk().is_none());
1242 assert_eq!(rel.get(), Some(&team));
1243 }
1244
1245 #[test]
1246 fn test_related_set_loaded_succeeds_first_time() {
1247 let rel = Related::<Team>::from_fk(1_i64);
1248 let team = Team {
1249 id: Some(1),
1250 name: "Avengers".to_string(),
1251 };
1252 assert!(rel.set_loaded(Some(team.clone())).is_ok());
1253 assert!(rel.is_loaded());
1254 assert_eq!(rel.get(), Some(&team));
1255 }
1256
1257 #[test]
1258 fn test_related_set_loaded_fails_second_time() {
1259 let rel = Related::<Team>::empty();
1260 assert!(rel.set_loaded(None).is_ok());
1261 assert!(rel.is_loaded());
1262 assert!(rel.set_loaded(None).is_err());
1263 }
1264
1265 #[test]
1266 fn test_related_default_is_empty() {
1267 let rel: Related<Team> = Related::default();
1268 assert!(rel.is_empty());
1269 }
1270
1271 #[test]
1272 fn test_related_clone_unloaded_is_unloaded() {
1273 let rel = Related::<Team>::from_fk(7_i64);
1274 let cloned = rel.clone();
1275 assert!(!cloned.is_loaded());
1276 assert_eq!(cloned.fk(), rel.fk());
1277 }
1278
1279 #[test]
1280 fn test_related_clone_loaded_preserves_object() {
1281 let team = Team {
1282 id: Some(1),
1283 name: "Avengers".to_string(),
1284 };
1285 let rel = Related::loaded(team.clone());
1286 let cloned = rel.clone();
1287 assert!(cloned.is_loaded());
1288 assert_eq!(cloned.get(), Some(&team));
1289 }
1290
1291 #[test]
1292 fn test_related_debug_output_shows_state() {
1293 let rel = Related::<Team>::from_fk(1_i64);
1294 let s = format!("{rel:?}");
1295 assert!(s.contains("state"));
1296 assert!(s.contains("unloaded"));
1297 }
1298
1299 #[test]
1300 fn test_related_serde_serialize_loaded_outputs_object() {
1301 let rel = Related::loaded(Team {
1302 id: Some(1),
1303 name: "Avengers".to_string(),
1304 });
1305 let json = serde_json::to_value(&rel).unwrap();
1306 assert_eq!(
1307 json,
1308 serde_json::json!({
1309 "id": 1,
1310 "name": "Avengers"
1311 })
1312 );
1313 }
1314
1315 #[test]
1316 fn test_related_serde_serialize_unloaded_outputs_null() {
1317 let rel = Related::<Team>::from_fk(1_i64);
1318 let json = serde_json::to_value(&rel).unwrap();
1319 assert_eq!(json, serde_json::Value::Null);
1320 }
1321
1322 #[test]
1323 fn test_related_serde_deserialize_object_creates_loaded() {
1324 let rel: Related<Team> = serde_json::from_value(serde_json::json!({
1325 "id": 1,
1326 "name": "Avengers"
1327 }))
1328 .unwrap();
1329
1330 let expected = Team {
1331 id: Some(1),
1332 name: "Avengers".to_string(),
1333 };
1334 assert!(rel.is_loaded());
1335 assert_eq!(rel.get(), Some(&expected));
1336 }
1337
1338 #[test]
1339 fn test_related_serde_deserialize_null_creates_empty() {
1340 let rel: Related<Team> = serde_json::from_value(serde_json::Value::Null).unwrap();
1341 assert!(rel.is_empty());
1342 assert!(!rel.is_loaded());
1343 assert!(rel.get().is_none());
1344 }
1345
1346 #[test]
1347 fn test_related_serde_roundtrip_preserves_data() {
1348 let rel = Related::loaded(Team {
1349 id: Some(1),
1350 name: "Avengers".to_string(),
1351 });
1352 let json = serde_json::to_string(&rel).unwrap();
1353 let decoded: Related<Team> = serde_json::from_str(&json).unwrap();
1354 assert!(decoded.is_loaded());
1355 assert_eq!(decoded.get(), rel.get());
1356 }
1357
1358 #[test]
1363 fn test_related_many_new_is_unloaded() {
1364 let rel: RelatedMany<Team> = RelatedMany::new("team_id");
1365 assert!(!rel.is_loaded());
1366 assert!(rel.get().is_none());
1367 assert_eq!(rel.len(), 0);
1368 assert!(rel.is_empty());
1369 }
1370
1371 #[test]
1372 fn test_related_many_set_loaded() {
1373 let rel: RelatedMany<Team> = RelatedMany::new("team_id");
1374 let teams = vec![
1375 Team {
1376 id: Some(1),
1377 name: "Avengers".to_string(),
1378 },
1379 Team {
1380 id: Some(2),
1381 name: "X-Men".to_string(),
1382 },
1383 ];
1384 assert!(rel.set_loaded(teams.clone()).is_ok());
1385 assert!(rel.is_loaded());
1386 assert_eq!(rel.len(), 2);
1387 assert!(!rel.is_empty());
1388 }
1389
1390 #[test]
1391 fn test_related_many_get_returns_slice() {
1392 let rel: RelatedMany<Team> = RelatedMany::new("team_id");
1393 let teams = vec![Team {
1394 id: Some(1),
1395 name: "Avengers".to_string(),
1396 }];
1397 rel.set_loaded(teams.clone()).unwrap();
1398 let slice = rel.get().unwrap();
1399 assert_eq!(slice.len(), 1);
1400 assert_eq!(slice[0].name, "Avengers");
1401 }
1402
1403 #[test]
1404 fn test_related_many_iter() {
1405 let rel: RelatedMany<Team> = RelatedMany::new("team_id");
1406 let teams = vec![
1407 Team {
1408 id: Some(1),
1409 name: "A".to_string(),
1410 },
1411 Team {
1412 id: Some(2),
1413 name: "B".to_string(),
1414 },
1415 ];
1416 rel.set_loaded(teams).unwrap();
1417 let names: Vec<_> = rel.iter().map(|t| t.name.as_str()).collect();
1418 assert_eq!(names, vec!["A", "B"]);
1419 }
1420
1421 #[test]
1422 fn test_related_many_default() {
1423 let rel: RelatedMany<Team> = RelatedMany::default();
1424 assert!(!rel.is_loaded());
1425 assert!(rel.is_empty());
1426 }
1427
1428 #[test]
1429 fn test_related_many_clone() {
1430 let rel: RelatedMany<Team> = RelatedMany::new("team_id");
1431 rel.set_loaded(vec![Team {
1432 id: Some(1),
1433 name: "Test".to_string(),
1434 }])
1435 .unwrap();
1436 let cloned = rel.clone();
1437 assert!(cloned.is_loaded());
1438 assert_eq!(cloned.len(), 1);
1439 }
1440
1441 #[test]
1442 fn test_related_many_debug() {
1443 let rel: RelatedMany<Team> = RelatedMany::new("team_id");
1444 let debug_str = format!("{:?}", rel);
1445 assert!(debug_str.contains("RelatedMany"));
1446 assert!(debug_str.contains("fk_column"));
1447 }
1448
1449 #[test]
1450 fn test_related_many_serde_serialize_loaded() {
1451 let rel: RelatedMany<Team> = RelatedMany::new("team_id");
1452 rel.set_loaded(vec![Team {
1453 id: Some(1),
1454 name: "A".to_string(),
1455 }])
1456 .unwrap();
1457 let json = serde_json::to_value(&rel).unwrap();
1458 assert!(json.is_array());
1459 assert_eq!(json.as_array().unwrap().len(), 1);
1460 }
1461
1462 #[test]
1463 fn test_related_many_serde_serialize_unloaded() {
1464 let rel: RelatedMany<Team> = RelatedMany::new("team_id");
1465 let json = serde_json::to_value(&rel).unwrap();
1466 assert!(json.is_array());
1467 assert!(json.as_array().unwrap().is_empty());
1468 }
1469
1470 #[test]
1471 fn test_related_many_serde_deserialize() {
1472 let rel: RelatedMany<Team> = serde_json::from_value(serde_json::json!([
1473 {"id": 1, "name": "A"},
1474 {"id": 2, "name": "B"}
1475 ]))
1476 .unwrap();
1477 assert!(rel.is_loaded());
1478 assert_eq!(rel.len(), 2);
1479 }
1480
1481 #[test]
1486 fn test_related_many_with_link_table() {
1487 let link = LinkTableInfo::new("hero_powers", "hero_id", "power_id");
1488 let rel: RelatedMany<Team> = RelatedMany::with_link_table(link);
1489
1490 assert!(rel.is_many_to_many());
1491 assert_eq!(rel.link_table().unwrap().table_name, "hero_powers");
1492 assert_eq!(rel.link_table().unwrap().local_column, "hero_id");
1493 assert_eq!(rel.link_table().unwrap().remote_column, "power_id");
1494 }
1495
1496 #[test]
1497 fn test_related_many_link_tracks_pending() {
1498 let rel: RelatedMany<Team> = RelatedMany::new("");
1499 let team = Team {
1500 id: Some(1),
1501 name: "A".to_string(),
1502 };
1503
1504 assert!(!rel.has_pending_ops());
1505 rel.link(&team);
1506 assert!(rel.has_pending_ops());
1507
1508 let pending = rel.take_pending_links();
1509 assert_eq!(pending.len(), 1);
1510 assert_eq!(pending[0], vec![Value::from(1_i64)]);
1511
1512 assert!(!rel.has_pending_ops());
1514 }
1515
1516 #[test]
1517 fn test_related_many_unlink_tracks_pending() {
1518 let rel: RelatedMany<Team> = RelatedMany::new("");
1519 let team = Team {
1520 id: Some(2),
1521 name: "B".to_string(),
1522 };
1523
1524 rel.unlink(&team);
1525 assert!(rel.has_pending_ops());
1526
1527 let pending = rel.take_pending_unlinks();
1528 assert_eq!(pending.len(), 1);
1529 assert_eq!(pending[0], vec![Value::from(2_i64)]);
1530 }
1531
1532 #[test]
1533 fn test_related_many_multiple_links() {
1534 let rel: RelatedMany<Team> = RelatedMany::new("");
1535 let team1 = Team {
1536 id: Some(1),
1537 name: "A".to_string(),
1538 };
1539 let team2 = Team {
1540 id: Some(2),
1541 name: "B".to_string(),
1542 };
1543
1544 rel.link(&team1);
1545 rel.link(&team2);
1546
1547 let pending = rel.take_pending_links();
1548 assert_eq!(pending.len(), 2);
1549 }
1550
1551 #[test]
1552 fn test_related_many_link_and_unlink_together() {
1553 let rel: RelatedMany<Team> = RelatedMany::new("");
1554 let team1 = Team {
1555 id: Some(1),
1556 name: "A".to_string(),
1557 };
1558 let team2 = Team {
1559 id: Some(2),
1560 name: "B".to_string(),
1561 };
1562
1563 rel.link(&team1);
1564 rel.unlink(&team2);
1565 assert!(rel.has_pending_ops());
1566
1567 let links = rel.take_pending_links();
1568 let unlinks = rel.take_pending_unlinks();
1569
1570 assert_eq!(links.len(), 1);
1571 assert_eq!(unlinks.len(), 1);
1572 assert!(!rel.has_pending_ops());
1573 }
1574
1575 #[test]
1576 fn test_related_many_clone_preserves_pending() {
1577 let rel: RelatedMany<Team> = RelatedMany::new("");
1578 let team = Team {
1579 id: Some(1),
1580 name: "A".to_string(),
1581 };
1582
1583 rel.link(&team);
1584 let cloned = rel.clone();
1585
1586 assert!(cloned.has_pending_ops());
1587 let pending = cloned.take_pending_links();
1588 assert_eq!(pending.len(), 1);
1589 }
1590
1591 #[test]
1592 fn test_related_many_set_parent_pk() {
1593 let mut rel: RelatedMany<Team> = RelatedMany::new("team_id");
1594 assert!(rel.parent_pk().is_none());
1595
1596 rel.set_parent_pk(42_i64);
1597 assert_eq!(rel.parent_pk(), Some(&Value::from(42_i64)));
1598 }
1599
1600 #[test]
1605 fn test_lazy_empty_has_no_fk() {
1606 let lazy = Lazy::<Team>::empty();
1607 assert!(lazy.fk().is_none());
1608 assert!(lazy.is_empty());
1609 assert!(!lazy.is_loaded());
1610 assert!(lazy.get().is_none());
1611 }
1612
1613 #[test]
1614 fn test_lazy_from_fk_stores_value() {
1615 let lazy = Lazy::<Team>::from_fk(42_i64);
1616 assert!(!lazy.is_empty());
1617 assert_eq!(lazy.fk(), Some(&Value::from(42_i64)));
1618 assert!(!lazy.is_loaded());
1619 assert!(lazy.get().is_none());
1620 }
1621
1622 #[test]
1623 fn test_lazy_not_loaded_initially() {
1624 let lazy = Lazy::<Team>::from_fk(1_i64);
1625 assert!(!lazy.is_loaded());
1626 }
1627
1628 #[test]
1629 fn test_lazy_loaded_creates_loaded_state() {
1630 let team = Team {
1631 id: Some(1),
1632 name: "Avengers".to_string(),
1633 };
1634 let lazy = Lazy::loaded(team.clone());
1635 assert!(lazy.is_loaded());
1636 assert!(lazy.fk().is_none()); assert_eq!(lazy.get(), Some(&team));
1638 }
1639
1640 #[test]
1641 fn test_lazy_set_loaded_succeeds_first_time() {
1642 let lazy = Lazy::<Team>::from_fk(1_i64);
1643 let team = Team {
1644 id: Some(1),
1645 name: "Avengers".to_string(),
1646 };
1647 assert!(lazy.set_loaded(Some(team.clone())).is_ok());
1648 assert!(lazy.is_loaded());
1649 assert_eq!(lazy.get(), Some(&team));
1650 }
1651
1652 #[test]
1653 fn test_lazy_set_loaded_fails_second_time() {
1654 let lazy = Lazy::<Team>::empty();
1655 assert!(lazy.set_loaded(None).is_ok());
1656 assert!(lazy.is_loaded());
1657 assert!(lazy.set_loaded(None).is_err());
1658 }
1659
1660 #[test]
1661 fn test_lazy_load_fetches_from_loader_and_caches() {
1662 #[derive(Default)]
1663 struct Loader {
1664 calls: usize,
1665 }
1666
1667 impl LazyLoader<Team> for Loader {
1668 fn get(
1669 &mut self,
1670 _cx: &Cx,
1671 pk: Value,
1672 ) -> impl Future<Output = Outcome<Option<Team>, Error>> + Send {
1673 self.calls += 1;
1674 let team = match pk {
1675 Value::BigInt(1) => Some(Team {
1676 id: Some(1),
1677 name: "Avengers".to_string(),
1678 }),
1679 _ => None,
1680 };
1681 async move { Outcome::Ok(team) }
1682 }
1683 }
1684
1685 let rt = RuntimeBuilder::current_thread()
1686 .build()
1687 .expect("create asupersync runtime");
1688 let cx = Cx::for_testing();
1689
1690 rt.block_on(async {
1691 let mut lazy = Lazy::<Team>::from_fk(1_i64);
1692 let mut loader = Loader::default();
1693
1694 let outcome = lazy.load(&cx, &mut loader).await;
1695 assert!(matches!(outcome, Outcome::Ok(Some(_))));
1696 assert!(lazy.is_loaded());
1697 assert_eq!(loader.calls, 1);
1698
1699 let outcome2 = lazy.load(&cx, &mut loader).await;
1701 assert!(matches!(outcome2, Outcome::Ok(Some(_))));
1702 assert_eq!(loader.calls, 1);
1703 });
1704 }
1705
1706 #[test]
1707 fn test_lazy_load_empty_returns_none_without_calling_loader() {
1708 #[derive(Default)]
1709 struct Loader {
1710 calls: usize,
1711 }
1712
1713 impl LazyLoader<Team> for Loader {
1714 fn get(
1715 &mut self,
1716 _cx: &Cx,
1717 _pk: Value,
1718 ) -> impl Future<Output = Outcome<Option<Team>, Error>> + Send {
1719 self.calls += 1;
1720 async { Outcome::Ok(None) }
1721 }
1722 }
1723
1724 let rt = RuntimeBuilder::current_thread()
1725 .build()
1726 .expect("create asupersync runtime");
1727 let cx = Cx::for_testing();
1728
1729 rt.block_on(async {
1730 let mut lazy = Lazy::<Team>::empty();
1731 let mut loader = Loader::default();
1732
1733 let outcome = lazy.load(&cx, &mut loader).await;
1734 assert!(matches!(outcome, Outcome::Ok(None)));
1735 assert!(lazy.is_loaded());
1736 assert_eq!(loader.calls, 0);
1737 });
1738 }
1739
1740 #[test]
1741 fn test_lazy_load_error_does_not_mark_loaded() {
1742 #[derive(Default)]
1743 struct Loader {
1744 calls: usize,
1745 }
1746
1747 impl LazyLoader<Team> for Loader {
1748 fn get(
1749 &mut self,
1750 _cx: &Cx,
1751 _pk: Value,
1752 ) -> impl Future<Output = Outcome<Option<Team>, Error>> + Send {
1753 self.calls += 1;
1754 async { Outcome::Err(Error::Custom("boom".to_string())) }
1755 }
1756 }
1757
1758 let rt = RuntimeBuilder::current_thread()
1759 .build()
1760 .expect("create asupersync runtime");
1761 let cx = Cx::for_testing();
1762
1763 rt.block_on(async {
1764 let mut lazy = Lazy::<Team>::from_fk(1_i64);
1765 let mut loader = Loader::default();
1766
1767 let outcome = lazy.load(&cx, &mut loader).await;
1768 assert!(matches!(outcome, Outcome::Err(_)));
1769 assert!(!lazy.is_loaded());
1770 assert_eq!(loader.calls, 1);
1771 });
1772 }
1773
1774 #[test]
1775 fn test_lazy_get_before_load_returns_none() {
1776 let lazy = Lazy::<Team>::from_fk(1_i64);
1777 assert!(lazy.get().is_none());
1778 }
1779
1780 #[test]
1781 fn test_lazy_default_is_empty() {
1782 let lazy: Lazy<Team> = Lazy::default();
1783 assert!(lazy.is_empty());
1784 assert!(!lazy.is_loaded());
1785 }
1786
1787 #[test]
1788 fn test_lazy_clone_unloaded_is_unloaded() {
1789 let lazy = Lazy::<Team>::from_fk(7_i64);
1790 let cloned = lazy.clone();
1791 assert!(!cloned.is_loaded());
1792 assert_eq!(cloned.fk(), lazy.fk());
1793 }
1794
1795 #[test]
1796 fn test_lazy_clone_loaded_preserves_object() {
1797 let team = Team {
1798 id: Some(1),
1799 name: "Avengers".to_string(),
1800 };
1801 let lazy = Lazy::loaded(team.clone());
1802 let cloned = lazy.clone();
1803 assert!(cloned.is_loaded());
1804 assert_eq!(cloned.get(), Some(&team));
1805 }
1806
1807 #[test]
1808 fn test_lazy_debug_output_shows_state() {
1809 let lazy = Lazy::<Team>::from_fk(1_i64);
1810 let s = format!("{lazy:?}");
1811 assert!(s.contains("state"));
1812 assert!(s.contains("unloaded"));
1813 }
1814
1815 #[test]
1816 fn test_lazy_serde_serialize_loaded_outputs_object() {
1817 let lazy = Lazy::loaded(Team {
1818 id: Some(1),
1819 name: "Avengers".to_string(),
1820 });
1821 let json = serde_json::to_value(&lazy).unwrap();
1822 assert_eq!(
1823 json,
1824 serde_json::json!({
1825 "id": 1,
1826 "name": "Avengers"
1827 })
1828 );
1829 }
1830
1831 #[test]
1832 fn test_lazy_serde_serialize_unloaded_outputs_null() {
1833 let lazy = Lazy::<Team>::from_fk(1_i64);
1834 let json = serde_json::to_value(&lazy).unwrap();
1835 assert_eq!(json, serde_json::Value::Null);
1836 }
1837
1838 #[test]
1839 fn test_lazy_serde_deserialize_object_creates_loaded() {
1840 let lazy: Lazy<Team> = serde_json::from_value(serde_json::json!({
1841 "id": 1,
1842 "name": "Avengers"
1843 }))
1844 .unwrap();
1845
1846 let expected = Team {
1847 id: Some(1),
1848 name: "Avengers".to_string(),
1849 };
1850 assert!(lazy.is_loaded());
1851 assert_eq!(lazy.get(), Some(&expected));
1852 }
1853
1854 #[test]
1855 fn test_lazy_serde_deserialize_null_creates_empty() {
1856 let lazy: Lazy<Team> = serde_json::from_value(serde_json::Value::Null).unwrap();
1857 assert!(lazy.is_empty());
1858 assert!(!lazy.is_loaded());
1859 assert!(lazy.get().is_none());
1860 }
1861
1862 #[test]
1863 fn test_lazy_serde_roundtrip_preserves_data() {
1864 let lazy = Lazy::loaded(Team {
1865 id: Some(1),
1866 name: "Avengers".to_string(),
1867 });
1868 let json = serde_json::to_string(&lazy).unwrap();
1869 let decoded: Lazy<Team> = serde_json::from_str(&json).unwrap();
1870 assert!(decoded.is_loaded());
1871 assert_eq!(decoded.get(), lazy.get());
1872 }
1873
1874 #[test]
1875 fn test_lazy_reset_clears_loaded_state() {
1876 let mut lazy = Lazy::loaded(Team {
1877 id: Some(1),
1878 name: "Test".to_string(),
1879 });
1880 assert!(lazy.is_loaded());
1881
1882 lazy.reset();
1883 assert!(!lazy.is_loaded());
1884 assert!(lazy.get().is_none());
1885 }
1886
1887 #[test]
1888 fn test_lazy_is_empty_accurate() {
1889 let empty = Lazy::<Team>::empty();
1890 assert!(empty.is_empty());
1891
1892 let with_fk = Lazy::<Team>::from_fk(1_i64);
1893 assert!(!with_fk.is_empty());
1894
1895 let loaded = Lazy::loaded(Team {
1896 id: Some(1),
1897 name: "Test".to_string(),
1898 });
1899 assert!(loaded.is_empty()); }
1901
1902 #[test]
1903 fn test_lazy_load_missing_object_caches_none() {
1904 let lazy = Lazy::<Team>::from_fk(999_i64);
1905 assert!(lazy.set_loaded(None).is_ok());
1907 assert!(lazy.is_loaded());
1908 assert!(lazy.get().is_none());
1909
1910 assert!(lazy.set_loaded(None).is_err());
1912 }
1913
1914 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1919 struct Hero {
1920 id: Option<i64>,
1921 name: String,
1922 }
1923
1924 impl Model for Hero {
1925 const TABLE_NAME: &'static str = "heroes";
1926 const PRIMARY_KEY: &'static [&'static str] = &["id"];
1927 const RELATIONSHIPS: &'static [RelationshipInfo] = &[RelationshipInfo {
1928 name: "team",
1929 related_table: "teams",
1930 kind: RelationshipKind::ManyToOne,
1931 local_key: Some("team_id"),
1932 remote_key: None,
1933 link_table: None,
1934 back_populates: Some("heroes"),
1935 lazy: false,
1936 cascade_delete: false,
1937 passive_deletes: PassiveDeletes::Active,
1938 order_by: None,
1939 lazy_strategy: None,
1940 cascade: None,
1941 uselist: None,
1942 }];
1943
1944 fn fields() -> &'static [FieldInfo] {
1945 &[]
1946 }
1947
1948 fn to_row(&self) -> Vec<(&'static str, Value)> {
1949 vec![]
1950 }
1951
1952 fn from_row(_row: &Row) -> Result<Self> {
1953 Ok(Self {
1954 id: None,
1955 name: String::new(),
1956 })
1957 }
1958
1959 fn primary_key_value(&self) -> Vec<Value> {
1960 match self.id {
1961 Some(id) => vec![Value::from(id)],
1962 None => vec![],
1963 }
1964 }
1965
1966 fn is_new(&self) -> bool {
1967 self.id.is_none()
1968 }
1969 }
1970
1971 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1973 struct TeamWithRelationships {
1974 id: Option<i64>,
1975 name: String,
1976 }
1977
1978 impl Model for TeamWithRelationships {
1979 const TABLE_NAME: &'static str = "teams";
1980 const PRIMARY_KEY: &'static [&'static str] = &["id"];
1981 const RELATIONSHIPS: &'static [RelationshipInfo] = &[RelationshipInfo {
1982 name: "heroes",
1983 related_table: "heroes",
1984 kind: RelationshipKind::OneToMany,
1985 local_key: None,
1986 remote_key: Some("team_id"),
1987 link_table: None,
1988 back_populates: Some("team"),
1989 lazy: false,
1990 cascade_delete: false,
1991 passive_deletes: PassiveDeletes::Active,
1992 order_by: None,
1993 lazy_strategy: None,
1994 cascade: None,
1995 uselist: None,
1996 }];
1997
1998 fn fields() -> &'static [FieldInfo] {
1999 &[]
2000 }
2001
2002 fn to_row(&self) -> Vec<(&'static str, Value)> {
2003 vec![]
2004 }
2005
2006 fn from_row(_row: &Row) -> Result<Self> {
2007 Ok(Self {
2008 id: None,
2009 name: String::new(),
2010 })
2011 }
2012
2013 fn primary_key_value(&self) -> Vec<Value> {
2014 match self.id {
2015 Some(id) => vec![Value::from(id)],
2016 None => vec![],
2017 }
2018 }
2019
2020 fn is_new(&self) -> bool {
2021 self.id.is_none()
2022 }
2023 }
2024
2025 #[test]
2026 fn test_find_relationship_found() {
2027 let rel = find_relationship::<Hero>("team");
2028 assert!(rel.is_some());
2029 let rel = rel.unwrap();
2030 assert_eq!(rel.name, "team");
2031 assert_eq!(rel.related_table, "teams");
2032 assert_eq!(rel.back_populates, Some("heroes"));
2033 }
2034
2035 #[test]
2036 fn test_find_relationship_not_found() {
2037 let rel = find_relationship::<Hero>("powers");
2038 assert!(rel.is_none());
2039 }
2040
2041 #[test]
2042 fn test_find_relationship_empty_relationships() {
2043 let rel = find_relationship::<Team>("heroes");
2045 assert!(rel.is_none());
2046 }
2047
2048 #[test]
2049 fn test_find_back_relationship_found() {
2050 let hero_team_rel = find_relationship::<Hero>("team").unwrap();
2051 let back = find_back_relationship(hero_team_rel, TeamWithRelationships::RELATIONSHIPS);
2052 assert!(back.is_some());
2053 let back = back.unwrap();
2054 assert_eq!(back.name, "heroes");
2055 assert_eq!(back.back_populates, Some("team"));
2056 }
2057
2058 #[test]
2059 fn test_find_back_relationship_no_back_populates() {
2060 let rel = RelationshipInfo::new("team", "teams", RelationshipKind::ManyToOne);
2061 let back = find_back_relationship(&rel, TeamWithRelationships::RELATIONSHIPS);
2062 assert!(back.is_none());
2063 }
2064
2065 #[test]
2066 fn test_validate_back_populates_valid() {
2067 let result = validate_back_populates::<Hero, TeamWithRelationships>("team");
2068 assert!(result.is_ok());
2069 }
2070
2071 #[test]
2072 fn test_validate_back_populates_no_source_relationship() {
2073 let result = validate_back_populates::<Hero, TeamWithRelationships>("nonexistent");
2074 assert!(result.is_err());
2075 assert!(result.unwrap_err().contains("No relationship"));
2076 }
2077
2078 #[test]
2079 fn test_validate_back_populates_no_target_relationship() {
2080 let result = validate_back_populates::<Hero, Team>("team");
2082 assert!(result.is_err());
2083 assert!(result.unwrap_err().contains("does not exist"));
2084 }
2085}