1use crate::field::FieldInfo;
9use crate::{Error, Model, Value};
10use asupersync::{Cx, Outcome};
11use serde::{Deserialize, Deserializer, Serialize, Serializer};
12use std::fmt;
13use std::future::Future;
14use std::sync::OnceLock;
15
16#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
18pub enum RelationshipKind {
19 OneToOne,
21 #[default]
23 ManyToOne,
24 OneToMany,
26 ManyToMany,
28}
29
30#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
35pub enum PassiveDeletes {
36 #[default]
38 Active,
39 Passive,
42 All,
45}
46
47#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
52pub enum LazyLoadStrategy {
53 #[default]
55 Select,
56 Joined,
58 Subquery,
60 Selectin,
62 Dynamic,
64 NoLoad,
66 RaiseOnSql,
68 WriteOnly,
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub struct LinkTableInfo {
75 pub table_name: &'static str,
77
78 pub local_column: &'static str,
80
81 pub remote_column: &'static str,
83
84 pub local_columns: Option<&'static [&'static str]>,
88
89 pub remote_columns: Option<&'static [&'static str]>,
93}
94
95impl LinkTableInfo {
96 #[must_use]
98 pub const fn new(
99 table_name: &'static str,
100 local_column: &'static str,
101 remote_column: &'static str,
102 ) -> Self {
103 Self {
104 table_name,
105 local_column,
106 remote_column,
107 local_columns: None,
108 remote_columns: None,
109 }
110 }
111
112 #[must_use]
118 pub const fn composite(
119 table_name: &'static str,
120 local_columns: &'static [&'static str],
121 remote_columns: &'static [&'static str],
122 ) -> Self {
123 Self {
124 table_name,
125 local_column: "",
126 remote_column: "",
127 local_columns: Some(local_columns),
128 remote_columns: Some(remote_columns),
129 }
130 }
131
132 #[must_use]
134 pub fn local_cols(&self) -> &[&'static str] {
135 if let Some(cols) = self.local_columns {
136 return cols;
137 }
138 if self.local_column.is_empty() {
139 return &[];
140 }
141 std::slice::from_ref(&self.local_column)
142 }
143
144 #[must_use]
146 pub fn remote_cols(&self) -> &[&'static str] {
147 if let Some(cols) = self.remote_columns {
148 return cols;
149 }
150 if self.remote_column.is_empty() {
151 return &[];
152 }
153 std::slice::from_ref(&self.remote_column)
154 }
155}
156
157#[derive(Debug, Clone, Copy)]
159pub struct RelationshipInfo {
160 pub name: &'static str,
162
163 pub related_table: &'static str,
165
166 pub kind: RelationshipKind,
168
169 pub local_key: Option<&'static str>,
172
173 pub local_keys: Option<&'static [&'static str]>,
177
178 pub remote_key: Option<&'static str>,
181
182 pub remote_keys: Option<&'static [&'static str]>,
186
187 pub link_table: Option<LinkTableInfo>,
189
190 pub back_populates: Option<&'static str>,
192
193 pub lazy: bool,
195
196 pub cascade_delete: bool,
198
199 pub passive_deletes: PassiveDeletes,
201
202 pub order_by: Option<&'static str>,
204
205 pub lazy_strategy: Option<LazyLoadStrategy>,
207
208 pub cascade: Option<&'static str>,
210
211 pub uselist: Option<bool>,
216
217 pub related_fields_fn: fn() -> &'static [FieldInfo],
223}
224
225impl PartialEq for RelationshipInfo {
226 fn eq(&self, other: &Self) -> bool {
227 self.name == other.name
230 && self.related_table == other.related_table
231 && self.kind == other.kind
232 && self.local_key_cols() == other.local_key_cols()
233 && self.remote_key_cols() == other.remote_key_cols()
234 && self.link_table == other.link_table
235 && self.back_populates == other.back_populates
236 && self.lazy == other.lazy
237 && self.cascade_delete == other.cascade_delete
238 && self.passive_deletes == other.passive_deletes
239 && self.order_by == other.order_by
240 && self.lazy_strategy == other.lazy_strategy
241 && self.cascade == other.cascade
242 && self.uselist == other.uselist
243 }
244}
245
246impl Eq for RelationshipInfo {}
247
248impl RelationshipInfo {
249 fn empty_related_fields() -> &'static [FieldInfo] {
250 &[]
251 }
252
253 #[must_use]
255 pub const fn new(
256 name: &'static str,
257 related_table: &'static str,
258 kind: RelationshipKind,
259 ) -> Self {
260 Self {
261 name,
262 related_table,
263 kind,
264 local_key: None,
265 local_keys: None,
266 remote_key: None,
267 remote_keys: None,
268 link_table: None,
269 back_populates: None,
270 lazy: false,
271 cascade_delete: false,
272 passive_deletes: PassiveDeletes::Active,
273 order_by: None,
274 lazy_strategy: None,
275 cascade: None,
276 uselist: None,
277 related_fields_fn: Self::empty_related_fields,
278 }
279 }
280
281 #[must_use]
285 pub fn local_key_cols(&self) -> &[&'static str] {
286 if let Some(keys) = self.local_keys {
287 return keys;
288 }
289 match &self.local_key {
290 Some(key) => std::slice::from_ref(key),
291 None => &[],
292 }
293 }
294
295 #[must_use]
299 pub fn remote_key_cols(&self) -> &[&'static str] {
300 if let Some(keys) = self.remote_keys {
301 return keys;
302 }
303 match &self.remote_key {
304 Some(key) => std::slice::from_ref(key),
305 None => &[],
306 }
307 }
308
309 #[must_use]
314 pub const fn related_fields(mut self, f: fn() -> &'static [FieldInfo]) -> Self {
315 self.related_fields_fn = f;
316 self
317 }
318
319 #[must_use]
321 pub const fn local_key(mut self, key: &'static str) -> Self {
322 self.local_key = Some(key);
323 self.local_keys = None;
324 self
325 }
326
327 #[must_use]
331 pub const fn local_keys(mut self, keys: &'static [&'static str]) -> Self {
332 self.local_keys = Some(keys);
333 self.local_key = None;
334 self
335 }
336
337 #[must_use]
339 pub const fn remote_key(mut self, key: &'static str) -> Self {
340 self.remote_key = Some(key);
341 self.remote_keys = None;
342 self
343 }
344
345 #[must_use]
349 pub const fn remote_keys(mut self, keys: &'static [&'static str]) -> Self {
350 self.remote_keys = Some(keys);
351 self.remote_key = None;
352 self
353 }
354
355 #[must_use]
357 pub const fn link_table(mut self, info: LinkTableInfo) -> Self {
358 self.link_table = Some(info);
359 self
360 }
361
362 #[must_use]
364 pub const fn back_populates(mut self, field: &'static str) -> Self {
365 self.back_populates = Some(field);
366 self
367 }
368
369 #[must_use]
371 pub const fn lazy(mut self, value: bool) -> Self {
372 self.lazy = value;
373 self
374 }
375
376 #[must_use]
378 pub const fn cascade_delete(mut self, value: bool) -> Self {
379 self.cascade_delete = value;
380 self
381 }
382
383 #[must_use]
389 pub const fn passive_deletes(mut self, value: PassiveDeletes) -> Self {
390 self.passive_deletes = value;
391 self
392 }
393
394 #[must_use]
396 pub const fn order_by(mut self, ordering: &'static str) -> Self {
397 self.order_by = Some(ordering);
398 self
399 }
400
401 #[must_use]
403 pub const fn order_by_opt(mut self, ordering: Option<&'static str>) -> Self {
404 self.order_by = ordering;
405 self
406 }
407
408 #[must_use]
410 pub const fn lazy_strategy(mut self, strategy: LazyLoadStrategy) -> Self {
411 self.lazy_strategy = Some(strategy);
412 self
413 }
414
415 #[must_use]
417 pub const fn lazy_strategy_opt(mut self, strategy: Option<LazyLoadStrategy>) -> Self {
418 self.lazy_strategy = strategy;
419 self
420 }
421
422 #[must_use]
424 pub const fn cascade(mut self, opts: &'static str) -> Self {
425 self.cascade = Some(opts);
426 self
427 }
428
429 #[must_use]
431 pub const fn cascade_opt(mut self, opts: Option<&'static str>) -> Self {
432 self.cascade = opts;
433 self
434 }
435
436 #[must_use]
438 pub const fn uselist(mut self, value: bool) -> Self {
439 self.uselist = Some(value);
440 self
441 }
442
443 #[must_use]
445 pub const fn uselist_opt(mut self, value: Option<bool>) -> Self {
446 self.uselist = value;
447 self
448 }
449
450 #[must_use]
452 pub const fn is_passive_deletes(&self) -> bool {
453 matches!(
454 self.passive_deletes,
455 PassiveDeletes::Passive | PassiveDeletes::All
456 )
457 }
458
459 #[must_use]
461 pub const fn is_passive_deletes_all(&self) -> bool {
462 matches!(self.passive_deletes, PassiveDeletes::All)
463 }
464}
465
466impl Default for RelationshipInfo {
467 fn default() -> Self {
468 Self::new("", "", RelationshipKind::default())
469 }
470}
471
472pub fn find_relationship<M: crate::Model>(field_name: &str) -> Option<&'static RelationshipInfo> {
485 M::RELATIONSHIPS.iter().find(|r| r.name == field_name)
486}
487
488pub fn find_back_relationship(
498 source_rel: &RelationshipInfo,
499 target_relationships: &'static [RelationshipInfo],
500) -> Option<&'static RelationshipInfo> {
501 let back_field = source_rel.back_populates?;
502 target_relationships.iter().find(|r| r.name == back_field)
503}
504
505pub fn validate_back_populates<Source: crate::Model, Target: crate::Model>(
512 source_field: &str,
513) -> Result<(), String> {
514 let source_rel = find_relationship::<Source>(source_field).ok_or_else(|| {
515 format!(
516 "No relationship '{}' on {}",
517 source_field,
518 Source::TABLE_NAME
519 )
520 })?;
521
522 let Some(back_field) = source_rel.back_populates else {
523 return Ok(());
525 };
526
527 let target_rel = find_relationship::<Target>(back_field).ok_or_else(|| {
528 format!(
529 "{}.{} has back_populates='{}' but {}.{} does not exist",
530 Source::TABLE_NAME,
531 source_field,
532 back_field,
533 Target::TABLE_NAME,
534 back_field
535 )
536 })?;
537
538 if let Some(target_back) = target_rel.back_populates {
540 if target_back != source_field {
541 return Err(format!(
542 "{}.{} has back_populates='{}' but {}.{} has back_populates='{}' (expected '{}')",
543 Source::TABLE_NAME,
544 source_field,
545 back_field,
546 Target::TABLE_NAME,
547 back_field,
548 target_back,
549 source_field
550 ));
551 }
552 }
553
554 Ok(())
555}
556
557pub trait LazyLoader<M: Model> {
563 fn get(&mut self, cx: &Cx, pk: Value)
565 -> impl Future<Output = Outcome<Option<M>, Error>> + Send;
566}
567
568pub struct Related<T: Model> {
575 fk_value: Option<Value>,
576 loaded: OnceLock<Option<T>>,
577}
578
579impl<T: Model> Related<T> {
580 #[must_use]
582 pub const fn empty() -> Self {
583 Self {
584 fk_value: None,
585 loaded: OnceLock::new(),
586 }
587 }
588
589 #[must_use]
591 pub fn from_fk(fk: impl Into<Value>) -> Self {
592 Self {
593 fk_value: Some(fk.into()),
594 loaded: OnceLock::new(),
595 }
596 }
597
598 #[must_use]
600 pub fn loaded(obj: T) -> Self {
601 let cell = OnceLock::new();
602 let _ = cell.set(Some(obj));
603 Self {
604 fk_value: None,
605 loaded: cell,
606 }
607 }
608
609 #[must_use]
611 pub fn get(&self) -> Option<&T> {
612 self.loaded.get().and_then(|o| o.as_ref())
613 }
614
615 #[must_use]
617 pub fn is_loaded(&self) -> bool {
618 self.loaded.get().is_some()
619 }
620
621 #[must_use]
623 pub fn is_empty(&self) -> bool {
624 self.fk_value.is_none()
625 }
626
627 #[must_use]
629 pub fn fk(&self) -> Option<&Value> {
630 self.fk_value.as_ref()
631 }
632
633 pub fn set_loaded(&self, obj: Option<T>) -> Result<(), Option<T>> {
635 self.loaded.set(obj)
636 }
637}
638
639impl<T: Model> Default for Related<T> {
640 fn default() -> Self {
641 Self::empty()
642 }
643}
644
645impl<T: Model + Clone> Clone for Related<T> {
646 fn clone(&self) -> Self {
647 let cloned = Self {
648 fk_value: self.fk_value.clone(),
649 loaded: OnceLock::new(),
650 };
651
652 if let Some(value) = self.loaded.get() {
653 let _ = cloned.loaded.set(value.clone());
654 }
655
656 cloned
657 }
658}
659
660impl<T: Model + fmt::Debug> fmt::Debug for Related<T> {
661 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
662 let state = if self.is_loaded() {
663 "loaded"
664 } else if self.is_empty() {
665 "empty"
666 } else {
667 "unloaded"
668 };
669
670 f.debug_struct("Related")
671 .field("state", &state)
672 .field("fk_value", &self.fk_value)
673 .field("loaded", &self.get())
674 .finish()
675 }
676}
677
678impl<T> Serialize for Related<T>
679where
680 T: Model + Serialize,
681{
682 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
683 match self.loaded.get() {
684 Some(Some(obj)) => obj.serialize(serializer),
685 Some(None) | None => serializer.serialize_none(),
686 }
687 }
688}
689
690impl<'de, T> Deserialize<'de> for Related<T>
691where
692 T: Model + Deserialize<'de>,
693{
694 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
695 let opt = Option::<T>::deserialize(deserializer)?;
696 Ok(match opt {
697 Some(obj) => Self::loaded(obj),
698 None => Self::empty(),
699 })
700 }
701}
702
703pub struct RelatedMany<T: Model> {
712 loaded: OnceLock<Vec<T>>,
714 fk_column: &'static str,
716 parent_pk: Option<Value>,
718 link_table: Option<LinkTableInfo>,
720 pending_links: std::sync::Mutex<Vec<Vec<Value>>>,
722 pending_unlinks: std::sync::Mutex<Vec<Vec<Value>>>,
724}
725
726impl<T: Model> RelatedMany<T> {
727 #[must_use]
732 pub fn new(fk_column: &'static str) -> Self {
733 Self {
734 loaded: OnceLock::new(),
735 fk_column,
736 parent_pk: None,
737 link_table: None,
738 pending_links: std::sync::Mutex::new(Vec::new()),
739 pending_unlinks: std::sync::Mutex::new(Vec::new()),
740 }
741 }
742
743 #[must_use]
752 pub fn with_link_table(link_table: LinkTableInfo) -> Self {
753 Self {
754 loaded: OnceLock::new(),
755 fk_column: "",
756 parent_pk: None,
757 link_table: Some(link_table),
758 pending_links: std::sync::Mutex::new(Vec::new()),
759 pending_unlinks: std::sync::Mutex::new(Vec::new()),
760 }
761 }
762
763 #[must_use]
765 pub fn with_parent_pk(fk_column: &'static str, pk: impl Into<Value>) -> Self {
766 Self {
767 loaded: OnceLock::new(),
768 fk_column,
769 parent_pk: Some(pk.into()),
770 link_table: None,
771 pending_links: std::sync::Mutex::new(Vec::new()),
772 pending_unlinks: std::sync::Mutex::new(Vec::new()),
773 }
774 }
775
776 #[must_use]
778 pub fn is_loaded(&self) -> bool {
779 self.loaded.get().is_some()
780 }
781
782 #[must_use]
784 pub fn get(&self) -> Option<&[T]> {
785 self.loaded.get().map(Vec::as_slice)
786 }
787
788 #[must_use]
790 pub fn len(&self) -> usize {
791 self.loaded.get().map_or(0, Vec::len)
792 }
793
794 #[must_use]
796 pub fn is_empty(&self) -> bool {
797 self.loaded.get().is_none_or(Vec::is_empty)
798 }
799
800 pub fn set_loaded(&self, objects: Vec<T>) -> Result<(), Vec<T>> {
802 self.loaded.set(objects)
803 }
804
805 pub fn iter(&self) -> impl Iterator<Item = &T> {
807 self.loaded.get().map_or([].iter(), |v| v.iter())
808 }
809
810 #[must_use]
812 pub fn fk_column(&self) -> &'static str {
813 self.fk_column
814 }
815
816 #[must_use]
818 pub fn parent_pk(&self) -> Option<&Value> {
819 self.parent_pk.as_ref()
820 }
821
822 pub fn set_parent_pk(&mut self, pk: impl Into<Value>) {
824 self.parent_pk = Some(pk.into());
825 }
826
827 #[must_use]
829 pub fn link_table(&self) -> Option<&LinkTableInfo> {
830 self.link_table.as_ref()
831 }
832
833 #[must_use]
835 pub fn is_many_to_many(&self) -> bool {
836 self.link_table.is_some()
837 }
838
839 pub fn link(&self, obj: &T) {
853 let pk = obj.primary_key_value();
854 match self.pending_links.lock() {
855 Ok(mut pending) => {
856 if !pending.contains(&pk) {
858 pending.push(pk);
859 }
860 }
861 Err(poisoned) => {
862 let mut pending = poisoned.into_inner();
865 if !pending.contains(&pk) {
866 pending.push(pk);
867 }
868 }
869 }
870 }
871
872 pub fn unlink(&self, obj: &T) {
886 let pk = obj.primary_key_value();
887 match self.pending_unlinks.lock() {
888 Ok(mut pending) => {
889 if !pending.contains(&pk) {
891 pending.push(pk);
892 }
893 }
894 Err(poisoned) => {
895 let mut pending = poisoned.into_inner();
898 if !pending.contains(&pk) {
899 pending.push(pk);
900 }
901 }
902 }
903 }
904
905 pub fn take_pending_links(&self) -> Vec<Vec<Value>> {
910 match self.pending_links.lock() {
911 Ok(mut v) => std::mem::take(&mut *v),
912 Err(poisoned) => {
913 std::mem::take(&mut *poisoned.into_inner())
915 }
916 }
917 }
918
919 pub fn take_pending_unlinks(&self) -> Vec<Vec<Value>> {
924 match self.pending_unlinks.lock() {
925 Ok(mut v) => std::mem::take(&mut *v),
926 Err(poisoned) => {
927 std::mem::take(&mut *poisoned.into_inner())
929 }
930 }
931 }
932
933 #[must_use]
935 pub fn has_pending_ops(&self) -> bool {
936 let has_links = match self.pending_links.lock() {
937 Ok(v) => !v.is_empty(),
938 Err(poisoned) => !poisoned.into_inner().is_empty(),
939 };
940 let has_unlinks = match self.pending_unlinks.lock() {
941 Ok(v) => !v.is_empty(),
942 Err(poisoned) => !poisoned.into_inner().is_empty(),
943 };
944 has_links || has_unlinks
945 }
946}
947
948impl<T: Model> Default for RelatedMany<T> {
949 fn default() -> Self {
950 Self::new("")
951 }
952}
953
954impl<T: Model + Clone> Clone for RelatedMany<T> {
955 fn clone(&self) -> Self {
956 let cloned_links = match self.pending_links.lock() {
958 Ok(v) => v.clone(),
959 Err(poisoned) => poisoned.into_inner().clone(),
960 };
961
962 let cloned_unlinks = match self.pending_unlinks.lock() {
964 Ok(v) => v.clone(),
965 Err(poisoned) => poisoned.into_inner().clone(),
966 };
967
968 let cloned = Self {
969 loaded: OnceLock::new(),
970 fk_column: self.fk_column,
971 parent_pk: self.parent_pk.clone(),
972 link_table: self.link_table,
973 pending_links: std::sync::Mutex::new(cloned_links),
974 pending_unlinks: std::sync::Mutex::new(cloned_unlinks),
975 };
976
977 if let Some(vec) = self.loaded.get() {
978 let _ = cloned.loaded.set(vec.clone());
979 }
980
981 cloned
982 }
983}
984
985impl<T: Model + fmt::Debug> fmt::Debug for RelatedMany<T> {
986 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
987 let pending_links_count = self.pending_links.lock().map_or(0, |v| v.len());
988 let pending_unlinks_count = self.pending_unlinks.lock().map_or(0, |v| v.len());
989
990 f.debug_struct("RelatedMany")
991 .field("loaded", &self.loaded.get())
992 .field("fk_column", &self.fk_column)
993 .field("parent_pk", &self.parent_pk)
994 .field("link_table", &self.link_table)
995 .field("pending_links_count", &pending_links_count)
996 .field("pending_unlinks_count", &pending_unlinks_count)
997 .finish()
998 }
999}
1000
1001impl<T> Serialize for RelatedMany<T>
1002where
1003 T: Model + Serialize,
1004{
1005 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1006 match self.loaded.get() {
1007 Some(vec) => vec.serialize(serializer),
1008 None => Vec::<T>::new().serialize(serializer),
1009 }
1010 }
1011}
1012
1013impl<'de, T> Deserialize<'de> for RelatedMany<T>
1014where
1015 T: Model + Deserialize<'de>,
1016{
1017 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1018 let vec = Vec::<T>::deserialize(deserializer)?;
1019 let rel = Self::new("");
1020 let _ = rel.loaded.set(vec);
1021 Ok(rel)
1022 }
1023}
1024
1025impl<'a, T: Model> IntoIterator for &'a RelatedMany<T> {
1026 type Item = &'a T;
1027 type IntoIter = std::slice::Iter<'a, T>;
1028
1029 fn into_iter(self) -> Self::IntoIter {
1030 self.loaded.get().map_or([].iter(), |v| v.iter())
1031 }
1032}
1033
1034pub struct Lazy<T: Model> {
1075 fk_value: Option<Value>,
1077 loaded: OnceLock<Option<T>>,
1079 load_attempted: std::sync::atomic::AtomicBool,
1081}
1082
1083impl<T: Model> Lazy<T> {
1084 #[must_use]
1086 pub fn empty() -> Self {
1087 Self {
1088 fk_value: None,
1089 loaded: OnceLock::new(),
1090 load_attempted: std::sync::atomic::AtomicBool::new(false),
1091 }
1092 }
1093
1094 #[must_use]
1096 pub fn from_fk(fk: impl Into<Value>) -> Self {
1097 Self {
1098 fk_value: Some(fk.into()),
1099 loaded: OnceLock::new(),
1100 load_attempted: std::sync::atomic::AtomicBool::new(false),
1101 }
1102 }
1103
1104 #[must_use]
1106 pub fn loaded(obj: T) -> Self {
1107 let cell = OnceLock::new();
1108 let _ = cell.set(Some(obj));
1109 Self {
1110 fk_value: None,
1111 loaded: cell,
1112 load_attempted: std::sync::atomic::AtomicBool::new(true),
1113 }
1114 }
1115
1116 pub async fn load<L>(&mut self, cx: &Cx, loader: &mut L) -> Outcome<Option<&T>, Error>
1122 where
1123 L: LazyLoader<T> + ?Sized,
1124 {
1125 if self.is_loaded() {
1126 return Outcome::Ok(self.get());
1127 }
1128
1129 let Some(fk) = self.fk_value.clone() else {
1130 let _ = self.set_loaded(None);
1131 return Outcome::Ok(None);
1132 };
1133
1134 match loader.get(cx, fk).await {
1135 Outcome::Ok(obj) => {
1136 let _ = self.set_loaded(obj);
1137 Outcome::Ok(self.get())
1138 }
1139 Outcome::Err(e) => Outcome::Err(e),
1140 Outcome::Cancelled(r) => Outcome::Cancelled(r),
1141 Outcome::Panicked(p) => Outcome::Panicked(p),
1142 }
1143 }
1144
1145 #[must_use]
1147 pub fn get(&self) -> Option<&T> {
1148 self.loaded.get().and_then(|o| o.as_ref())
1149 }
1150
1151 #[must_use]
1153 pub fn is_loaded(&self) -> bool {
1154 self.load_attempted
1155 .load(std::sync::atomic::Ordering::Acquire)
1156 }
1157
1158 #[must_use]
1160 pub fn is_empty(&self) -> bool {
1161 self.fk_value.is_none()
1162 }
1163
1164 #[must_use]
1166 pub fn fk(&self) -> Option<&Value> {
1167 self.fk_value.as_ref()
1168 }
1169
1170 pub fn set_loaded(&self, obj: Option<T>) -> Result<(), Option<T>> {
1174 match self.loaded.set(obj) {
1175 Ok(()) => {
1176 self.load_attempted
1177 .store(true, std::sync::atomic::Ordering::Release);
1178 Ok(())
1179 }
1180 Err(v) => Err(v),
1181 }
1182 }
1183
1184 pub fn reset(&mut self) {
1188 self.loaded = OnceLock::new();
1189 self.load_attempted = std::sync::atomic::AtomicBool::new(false);
1190 }
1191}
1192
1193impl<T: Model> Default for Lazy<T> {
1194 fn default() -> Self {
1195 Self::empty()
1196 }
1197}
1198
1199impl<T: Model + Clone> Clone for Lazy<T> {
1200 fn clone(&self) -> Self {
1201 let cloned = Self {
1202 fk_value: self.fk_value.clone(),
1203 loaded: OnceLock::new(),
1204 load_attempted: std::sync::atomic::AtomicBool::new(
1205 self.load_attempted
1206 .load(std::sync::atomic::Ordering::Acquire),
1207 ),
1208 };
1209
1210 if let Some(value) = self.loaded.get() {
1211 let _ = cloned.loaded.set(value.clone());
1212 }
1213
1214 cloned
1215 }
1216}
1217
1218impl<T: Model + fmt::Debug> fmt::Debug for Lazy<T> {
1219 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1220 let state = if self.is_loaded() {
1221 "loaded"
1222 } else if self.is_empty() {
1223 "empty"
1224 } else {
1225 "unloaded"
1226 };
1227
1228 f.debug_struct("Lazy")
1229 .field("state", &state)
1230 .field("fk_value", &self.fk_value)
1231 .field("loaded", &self.get())
1232 .field(
1233 "load_attempted",
1234 &self
1235 .load_attempted
1236 .load(std::sync::atomic::Ordering::Acquire),
1237 )
1238 .finish()
1239 }
1240}
1241
1242impl<T> Serialize for Lazy<T>
1243where
1244 T: Model + Serialize,
1245{
1246 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1247 match self.loaded.get() {
1248 Some(Some(obj)) => obj.serialize(serializer),
1249 Some(None) | None => serializer.serialize_none(),
1250 }
1251 }
1252}
1253
1254impl<'de, T> Deserialize<'de> for Lazy<T>
1255where
1256 T: Model + Deserialize<'de>,
1257{
1258 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1259 let opt = Option::<T>::deserialize(deserializer)?;
1260 Ok(match opt {
1261 Some(obj) => Self::loaded(obj),
1262 None => Self::empty(),
1263 })
1264 }
1265}
1266
1267#[cfg(test)]
1268mod tests {
1269 use super::*;
1270 use crate::{FieldInfo, Result, Row};
1271 use asupersync::runtime::RuntimeBuilder;
1272 use serde::{Deserialize, Serialize};
1273
1274 #[test]
1275 fn test_relationship_kind_default() {
1276 assert_eq!(RelationshipKind::default(), RelationshipKind::ManyToOne);
1277 }
1278
1279 #[test]
1280 fn test_relationship_info_builder_chain() {
1281 let info = RelationshipInfo::new("team", "teams", RelationshipKind::ManyToOne)
1282 .local_key("team_id")
1283 .back_populates("heroes")
1284 .lazy(true)
1285 .cascade_delete(true)
1286 .passive_deletes(PassiveDeletes::Passive);
1287
1288 assert_eq!(info.name, "team");
1289 assert_eq!(info.related_table, "teams");
1290 assert_eq!(info.kind, RelationshipKind::ManyToOne);
1291 assert_eq!(info.local_key, Some("team_id"));
1292 assert_eq!(info.remote_key, None);
1293 assert_eq!(info.link_table, None);
1294 assert_eq!(info.back_populates, Some("heroes"));
1295 assert!(info.lazy);
1296 assert!(info.cascade_delete);
1297 assert_eq!(info.passive_deletes, PassiveDeletes::Passive);
1298 }
1299
1300 #[test]
1301 fn test_passive_deletes_default() {
1302 assert_eq!(PassiveDeletes::default(), PassiveDeletes::Active);
1303 }
1304
1305 #[test]
1306 fn test_passive_deletes_helper_methods() {
1307 let active_info = RelationshipInfo::new("test", "test", RelationshipKind::OneToMany)
1309 .passive_deletes(PassiveDeletes::Active);
1310 assert!(!active_info.is_passive_deletes());
1311 assert!(!active_info.is_passive_deletes_all());
1312
1313 let passive_info = RelationshipInfo::new("test", "test", RelationshipKind::OneToMany)
1315 .passive_deletes(PassiveDeletes::Passive);
1316 assert!(passive_info.is_passive_deletes());
1317 assert!(!passive_info.is_passive_deletes_all());
1318
1319 let all_info = RelationshipInfo::new("test", "test", RelationshipKind::OneToMany)
1321 .passive_deletes(PassiveDeletes::All);
1322 assert!(all_info.is_passive_deletes());
1323 assert!(all_info.is_passive_deletes_all());
1324 }
1325
1326 #[test]
1327 fn test_relationship_info_new_has_active_passive_deletes() {
1328 let info = RelationshipInfo::new("test", "test", RelationshipKind::ManyToOne);
1330 assert_eq!(info.passive_deletes, PassiveDeletes::Active);
1331 assert!(!info.is_passive_deletes());
1332 }
1333
1334 #[test]
1335 fn test_link_table_info_new() {
1336 let link = LinkTableInfo::new("hero_powers", "hero_id", "power_id");
1337 assert_eq!(link.table_name, "hero_powers");
1338 assert_eq!(link.local_column, "hero_id");
1339 assert_eq!(link.remote_column, "power_id");
1340 }
1341
1342 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1343 struct Team {
1344 id: Option<i64>,
1345 name: String,
1346 }
1347
1348 impl Model for Team {
1349 const TABLE_NAME: &'static str = "teams";
1350 const PRIMARY_KEY: &'static [&'static str] = &["id"];
1351
1352 fn fields() -> &'static [FieldInfo] {
1353 &[]
1354 }
1355
1356 fn to_row(&self) -> Vec<(&'static str, Value)> {
1357 vec![]
1358 }
1359
1360 fn from_row(_row: &Row) -> Result<Self> {
1361 Ok(Self {
1362 id: None,
1363 name: String::new(),
1364 })
1365 }
1366
1367 fn primary_key_value(&self) -> Vec<Value> {
1368 match self.id {
1369 Some(id) => vec![Value::from(id)],
1370 None => vec![],
1371 }
1372 }
1373
1374 fn is_new(&self) -> bool {
1375 self.id.is_none()
1376 }
1377 }
1378
1379 #[test]
1380 fn test_related_empty_creates_unloaded_state() {
1381 let rel = Related::<Team>::empty();
1382 assert!(rel.is_empty());
1383 assert!(!rel.is_loaded());
1384 assert!(rel.get().is_none());
1385 assert!(rel.fk().is_none());
1386 }
1387
1388 #[test]
1389 fn test_related_from_fk_stores_value() {
1390 let rel = Related::<Team>::from_fk(42_i64);
1391 assert!(!rel.is_empty());
1392 assert_eq!(rel.fk(), Some(&Value::from(42_i64)));
1393 assert!(!rel.is_loaded());
1394 assert!(rel.get().is_none());
1395 }
1396
1397 #[test]
1398 fn test_related_loaded_sets_object() {
1399 let team = Team {
1400 id: Some(1),
1401 name: "Avengers".to_string(),
1402 };
1403 let rel = Related::loaded(team.clone());
1404 assert!(rel.is_loaded());
1405 assert!(rel.fk().is_none());
1406 assert_eq!(rel.get(), Some(&team));
1407 }
1408
1409 #[test]
1410 fn test_related_set_loaded_succeeds_first_time() {
1411 let rel = Related::<Team>::from_fk(1_i64);
1412 let team = Team {
1413 id: Some(1),
1414 name: "Avengers".to_string(),
1415 };
1416 assert!(rel.set_loaded(Some(team.clone())).is_ok());
1417 assert!(rel.is_loaded());
1418 assert_eq!(rel.get(), Some(&team));
1419 }
1420
1421 #[test]
1422 fn test_related_set_loaded_fails_second_time() {
1423 let rel = Related::<Team>::empty();
1424 assert!(rel.set_loaded(None).is_ok());
1425 assert!(rel.is_loaded());
1426 assert!(rel.set_loaded(None).is_err());
1427 }
1428
1429 #[test]
1430 fn test_related_default_is_empty() {
1431 let rel: Related<Team> = Related::default();
1432 assert!(rel.is_empty());
1433 }
1434
1435 #[test]
1436 fn test_related_clone_unloaded_is_unloaded() {
1437 let rel = Related::<Team>::from_fk(7_i64);
1438 let cloned = rel.clone();
1439 assert!(!cloned.is_loaded());
1440 assert_eq!(cloned.fk(), rel.fk());
1441 }
1442
1443 #[test]
1444 fn test_related_clone_loaded_preserves_object() {
1445 let team = Team {
1446 id: Some(1),
1447 name: "Avengers".to_string(),
1448 };
1449 let rel = Related::loaded(team.clone());
1450 let cloned = rel.clone();
1451 assert!(cloned.is_loaded());
1452 assert_eq!(cloned.get(), Some(&team));
1453 }
1454
1455 #[test]
1456 fn test_related_debug_output_shows_state() {
1457 let rel = Related::<Team>::from_fk(1_i64);
1458 let s = format!("{rel:?}");
1459 assert!(s.contains("state"));
1460 assert!(s.contains("unloaded"));
1461 }
1462
1463 #[test]
1464 fn test_related_serde_serialize_loaded_outputs_object() {
1465 let rel = Related::loaded(Team {
1466 id: Some(1),
1467 name: "Avengers".to_string(),
1468 });
1469 let json = serde_json::to_value(&rel).unwrap();
1470 assert_eq!(
1471 json,
1472 serde_json::json!({
1473 "id": 1,
1474 "name": "Avengers"
1475 })
1476 );
1477 }
1478
1479 #[test]
1480 fn test_related_serde_serialize_unloaded_outputs_null() {
1481 let rel = Related::<Team>::from_fk(1_i64);
1482 let json = serde_json::to_value(&rel).unwrap();
1483 assert_eq!(json, serde_json::Value::Null);
1484 }
1485
1486 #[test]
1487 fn test_related_serde_deserialize_object_creates_loaded() {
1488 let rel: Related<Team> = serde_json::from_value(serde_json::json!({
1489 "id": 1,
1490 "name": "Avengers"
1491 }))
1492 .unwrap();
1493
1494 let expected = Team {
1495 id: Some(1),
1496 name: "Avengers".to_string(),
1497 };
1498 assert!(rel.is_loaded());
1499 assert_eq!(rel.get(), Some(&expected));
1500 }
1501
1502 #[test]
1503 fn test_related_serde_deserialize_null_creates_empty() {
1504 let rel: Related<Team> = serde_json::from_value(serde_json::Value::Null).unwrap();
1505 assert!(rel.is_empty());
1506 assert!(!rel.is_loaded());
1507 assert!(rel.get().is_none());
1508 }
1509
1510 #[test]
1511 fn test_related_serde_roundtrip_preserves_data() {
1512 let rel = Related::loaded(Team {
1513 id: Some(1),
1514 name: "Avengers".to_string(),
1515 });
1516 let json = serde_json::to_string(&rel).unwrap();
1517 let decoded: Related<Team> = serde_json::from_str(&json).unwrap();
1518 assert!(decoded.is_loaded());
1519 assert_eq!(decoded.get(), rel.get());
1520 }
1521
1522 #[test]
1527 fn test_related_many_new_is_unloaded() {
1528 let rel: RelatedMany<Team> = RelatedMany::new("team_id");
1529 assert!(!rel.is_loaded());
1530 assert!(rel.get().is_none());
1531 assert_eq!(rel.len(), 0);
1532 assert!(rel.is_empty());
1533 }
1534
1535 #[test]
1536 fn test_related_many_set_loaded() {
1537 let rel: RelatedMany<Team> = RelatedMany::new("team_id");
1538 let teams = vec![
1539 Team {
1540 id: Some(1),
1541 name: "Avengers".to_string(),
1542 },
1543 Team {
1544 id: Some(2),
1545 name: "X-Men".to_string(),
1546 },
1547 ];
1548 assert!(rel.set_loaded(teams.clone()).is_ok());
1549 assert!(rel.is_loaded());
1550 assert_eq!(rel.len(), 2);
1551 assert!(!rel.is_empty());
1552 }
1553
1554 #[test]
1555 fn test_related_many_get_returns_slice() {
1556 let rel: RelatedMany<Team> = RelatedMany::new("team_id");
1557 let teams = vec![Team {
1558 id: Some(1),
1559 name: "Avengers".to_string(),
1560 }];
1561 rel.set_loaded(teams.clone()).unwrap();
1562 let slice = rel.get().unwrap();
1563 assert_eq!(slice.len(), 1);
1564 assert_eq!(slice[0].name, "Avengers");
1565 }
1566
1567 #[test]
1568 fn test_related_many_iter() {
1569 let rel: RelatedMany<Team> = RelatedMany::new("team_id");
1570 let teams = vec![
1571 Team {
1572 id: Some(1),
1573 name: "A".to_string(),
1574 },
1575 Team {
1576 id: Some(2),
1577 name: "B".to_string(),
1578 },
1579 ];
1580 rel.set_loaded(teams).unwrap();
1581 let names: Vec<_> = rel.iter().map(|t| t.name.as_str()).collect();
1582 assert_eq!(names, vec!["A", "B"]);
1583 }
1584
1585 #[test]
1586 fn test_related_many_default() {
1587 let rel: RelatedMany<Team> = RelatedMany::default();
1588 assert!(!rel.is_loaded());
1589 assert!(rel.is_empty());
1590 }
1591
1592 #[test]
1593 fn test_related_many_clone() {
1594 let rel: RelatedMany<Team> = RelatedMany::new("team_id");
1595 rel.set_loaded(vec![Team {
1596 id: Some(1),
1597 name: "Test".to_string(),
1598 }])
1599 .unwrap();
1600 let cloned = rel.clone();
1601 assert!(cloned.is_loaded());
1602 assert_eq!(cloned.len(), 1);
1603 }
1604
1605 #[test]
1606 fn test_related_many_debug() {
1607 let rel: RelatedMany<Team> = RelatedMany::new("team_id");
1608 let debug_str = format!("{:?}", rel);
1609 assert!(debug_str.contains("RelatedMany"));
1610 assert!(debug_str.contains("fk_column"));
1611 }
1612
1613 #[test]
1614 fn test_related_many_serde_serialize_loaded() {
1615 let rel: RelatedMany<Team> = RelatedMany::new("team_id");
1616 rel.set_loaded(vec![Team {
1617 id: Some(1),
1618 name: "A".to_string(),
1619 }])
1620 .unwrap();
1621 let json = serde_json::to_value(&rel).unwrap();
1622 assert!(json.is_array());
1623 assert_eq!(json.as_array().unwrap().len(), 1);
1624 }
1625
1626 #[test]
1627 fn test_related_many_serde_serialize_unloaded() {
1628 let rel: RelatedMany<Team> = RelatedMany::new("team_id");
1629 let json = serde_json::to_value(&rel).unwrap();
1630 assert!(json.is_array());
1631 assert!(json.as_array().unwrap().is_empty());
1632 }
1633
1634 #[test]
1635 fn test_related_many_serde_deserialize() {
1636 let rel: RelatedMany<Team> = serde_json::from_value(serde_json::json!([
1637 {"id": 1, "name": "A"},
1638 {"id": 2, "name": "B"}
1639 ]))
1640 .unwrap();
1641 assert!(rel.is_loaded());
1642 assert_eq!(rel.len(), 2);
1643 }
1644
1645 #[test]
1650 fn test_related_many_with_link_table() {
1651 let link = LinkTableInfo::new("hero_powers", "hero_id", "power_id");
1652 let rel: RelatedMany<Team> = RelatedMany::with_link_table(link);
1653
1654 assert!(rel.is_many_to_many());
1655 assert_eq!(rel.link_table().unwrap().table_name, "hero_powers");
1656 assert_eq!(rel.link_table().unwrap().local_column, "hero_id");
1657 assert_eq!(rel.link_table().unwrap().remote_column, "power_id");
1658 }
1659
1660 #[test]
1661 fn test_related_many_link_tracks_pending() {
1662 let rel: RelatedMany<Team> = RelatedMany::new("");
1663 let team = Team {
1664 id: Some(1),
1665 name: "A".to_string(),
1666 };
1667
1668 assert!(!rel.has_pending_ops());
1669 rel.link(&team);
1670 assert!(rel.has_pending_ops());
1671
1672 let pending = rel.take_pending_links();
1673 assert_eq!(pending.len(), 1);
1674 assert_eq!(pending[0], vec![Value::from(1_i64)]);
1675
1676 assert!(!rel.has_pending_ops());
1678 }
1679
1680 #[test]
1681 fn test_related_many_unlink_tracks_pending() {
1682 let rel: RelatedMany<Team> = RelatedMany::new("");
1683 let team = Team {
1684 id: Some(2),
1685 name: "B".to_string(),
1686 };
1687
1688 rel.unlink(&team);
1689 assert!(rel.has_pending_ops());
1690
1691 let pending = rel.take_pending_unlinks();
1692 assert_eq!(pending.len(), 1);
1693 assert_eq!(pending[0], vec![Value::from(2_i64)]);
1694 }
1695
1696 #[test]
1697 fn test_related_many_multiple_links() {
1698 let rel: RelatedMany<Team> = RelatedMany::new("");
1699 let team1 = Team {
1700 id: Some(1),
1701 name: "A".to_string(),
1702 };
1703 let team2 = Team {
1704 id: Some(2),
1705 name: "B".to_string(),
1706 };
1707
1708 rel.link(&team1);
1709 rel.link(&team2);
1710
1711 let pending = rel.take_pending_links();
1712 assert_eq!(pending.len(), 2);
1713 }
1714
1715 #[test]
1716 fn test_related_many_link_and_unlink_together() {
1717 let rel: RelatedMany<Team> = RelatedMany::new("");
1718 let team1 = Team {
1719 id: Some(1),
1720 name: "A".to_string(),
1721 };
1722 let team2 = Team {
1723 id: Some(2),
1724 name: "B".to_string(),
1725 };
1726
1727 rel.link(&team1);
1728 rel.unlink(&team2);
1729 assert!(rel.has_pending_ops());
1730
1731 let links = rel.take_pending_links();
1732 let unlinks = rel.take_pending_unlinks();
1733
1734 assert_eq!(links.len(), 1);
1735 assert_eq!(unlinks.len(), 1);
1736 assert!(!rel.has_pending_ops());
1737 }
1738
1739 #[test]
1740 fn test_related_many_clone_preserves_pending() {
1741 let rel: RelatedMany<Team> = RelatedMany::new("");
1742 let team = Team {
1743 id: Some(1),
1744 name: "A".to_string(),
1745 };
1746
1747 rel.link(&team);
1748 let cloned = rel.clone();
1749
1750 assert!(cloned.has_pending_ops());
1751 let pending = cloned.take_pending_links();
1752 assert_eq!(pending.len(), 1);
1753 }
1754
1755 #[test]
1756 fn test_related_many_set_parent_pk() {
1757 let mut rel: RelatedMany<Team> = RelatedMany::new("team_id");
1758 assert!(rel.parent_pk().is_none());
1759
1760 rel.set_parent_pk(42_i64);
1761 assert_eq!(rel.parent_pk(), Some(&Value::from(42_i64)));
1762 }
1763
1764 #[test]
1769 fn test_lazy_empty_has_no_fk() {
1770 let lazy = Lazy::<Team>::empty();
1771 assert!(lazy.fk().is_none());
1772 assert!(lazy.is_empty());
1773 assert!(!lazy.is_loaded());
1774 assert!(lazy.get().is_none());
1775 }
1776
1777 #[test]
1778 fn test_lazy_from_fk_stores_value() {
1779 let lazy = Lazy::<Team>::from_fk(42_i64);
1780 assert!(!lazy.is_empty());
1781 assert_eq!(lazy.fk(), Some(&Value::from(42_i64)));
1782 assert!(!lazy.is_loaded());
1783 assert!(lazy.get().is_none());
1784 }
1785
1786 #[test]
1787 fn test_lazy_not_loaded_initially() {
1788 let lazy = Lazy::<Team>::from_fk(1_i64);
1789 assert!(!lazy.is_loaded());
1790 }
1791
1792 #[test]
1793 fn test_lazy_loaded_creates_loaded_state() {
1794 let team = Team {
1795 id: Some(1),
1796 name: "Avengers".to_string(),
1797 };
1798 let lazy = Lazy::loaded(team.clone());
1799 assert!(lazy.is_loaded());
1800 assert!(lazy.fk().is_none()); assert_eq!(lazy.get(), Some(&team));
1802 }
1803
1804 #[test]
1805 fn test_lazy_set_loaded_succeeds_first_time() {
1806 let lazy = Lazy::<Team>::from_fk(1_i64);
1807 let team = Team {
1808 id: Some(1),
1809 name: "Avengers".to_string(),
1810 };
1811 assert!(lazy.set_loaded(Some(team.clone())).is_ok());
1812 assert!(lazy.is_loaded());
1813 assert_eq!(lazy.get(), Some(&team));
1814 }
1815
1816 #[test]
1817 fn test_lazy_set_loaded_fails_second_time() {
1818 let lazy = Lazy::<Team>::empty();
1819 assert!(lazy.set_loaded(None).is_ok());
1820 assert!(lazy.is_loaded());
1821 assert!(lazy.set_loaded(None).is_err());
1822 }
1823
1824 #[test]
1825 fn test_lazy_load_fetches_from_loader_and_caches() {
1826 #[derive(Default)]
1827 struct Loader {
1828 calls: usize,
1829 }
1830
1831 impl LazyLoader<Team> for Loader {
1832 fn get(
1833 &mut self,
1834 _cx: &Cx,
1835 pk: Value,
1836 ) -> impl Future<Output = Outcome<Option<Team>, Error>> + Send {
1837 self.calls += 1;
1838 let team = match pk {
1839 Value::BigInt(1) => Some(Team {
1840 id: Some(1),
1841 name: "Avengers".to_string(),
1842 }),
1843 _ => None,
1844 };
1845 async move { Outcome::Ok(team) }
1846 }
1847 }
1848
1849 let rt = RuntimeBuilder::current_thread()
1850 .build()
1851 .expect("create asupersync runtime");
1852 let cx = Cx::for_testing();
1853
1854 rt.block_on(async {
1855 let mut lazy = Lazy::<Team>::from_fk(1_i64);
1856 let mut loader = Loader::default();
1857
1858 let outcome = lazy.load(&cx, &mut loader).await;
1859 assert!(matches!(outcome, Outcome::Ok(Some(_))));
1860 assert!(lazy.is_loaded());
1861 assert_eq!(loader.calls, 1);
1862
1863 let outcome2 = lazy.load(&cx, &mut loader).await;
1865 assert!(matches!(outcome2, Outcome::Ok(Some(_))));
1866 assert_eq!(loader.calls, 1);
1867 });
1868 }
1869
1870 #[test]
1871 fn test_lazy_load_empty_returns_none_without_calling_loader() {
1872 #[derive(Default)]
1873 struct Loader {
1874 calls: usize,
1875 }
1876
1877 impl LazyLoader<Team> for Loader {
1878 fn get(
1879 &mut self,
1880 _cx: &Cx,
1881 _pk: Value,
1882 ) -> impl Future<Output = Outcome<Option<Team>, Error>> + Send {
1883 self.calls += 1;
1884 async { Outcome::Ok(None) }
1885 }
1886 }
1887
1888 let rt = RuntimeBuilder::current_thread()
1889 .build()
1890 .expect("create asupersync runtime");
1891 let cx = Cx::for_testing();
1892
1893 rt.block_on(async {
1894 let mut lazy = Lazy::<Team>::empty();
1895 let mut loader = Loader::default();
1896
1897 let outcome = lazy.load(&cx, &mut loader).await;
1898 assert!(matches!(outcome, Outcome::Ok(None)));
1899 assert!(lazy.is_loaded());
1900 assert_eq!(loader.calls, 0);
1901 });
1902 }
1903
1904 #[test]
1905 fn test_lazy_load_error_does_not_mark_loaded() {
1906 #[derive(Default)]
1907 struct Loader {
1908 calls: usize,
1909 }
1910
1911 impl LazyLoader<Team> for Loader {
1912 fn get(
1913 &mut self,
1914 _cx: &Cx,
1915 _pk: Value,
1916 ) -> impl Future<Output = Outcome<Option<Team>, Error>> + Send {
1917 self.calls += 1;
1918 async { Outcome::Err(Error::Custom("boom".to_string())) }
1919 }
1920 }
1921
1922 let rt = RuntimeBuilder::current_thread()
1923 .build()
1924 .expect("create asupersync runtime");
1925 let cx = Cx::for_testing();
1926
1927 rt.block_on(async {
1928 let mut lazy = Lazy::<Team>::from_fk(1_i64);
1929 let mut loader = Loader::default();
1930
1931 let outcome = lazy.load(&cx, &mut loader).await;
1932 assert!(matches!(outcome, Outcome::Err(_)));
1933 assert!(!lazy.is_loaded());
1934 assert_eq!(loader.calls, 1);
1935 });
1936 }
1937
1938 #[test]
1939 fn test_lazy_get_before_load_returns_none() {
1940 let lazy = Lazy::<Team>::from_fk(1_i64);
1941 assert!(lazy.get().is_none());
1942 }
1943
1944 #[test]
1945 fn test_lazy_default_is_empty() {
1946 let lazy: Lazy<Team> = Lazy::default();
1947 assert!(lazy.is_empty());
1948 assert!(!lazy.is_loaded());
1949 }
1950
1951 #[test]
1952 fn test_lazy_clone_unloaded_is_unloaded() {
1953 let lazy = Lazy::<Team>::from_fk(7_i64);
1954 let cloned = lazy.clone();
1955 assert!(!cloned.is_loaded());
1956 assert_eq!(cloned.fk(), lazy.fk());
1957 }
1958
1959 #[test]
1960 fn test_lazy_clone_loaded_preserves_object() {
1961 let team = Team {
1962 id: Some(1),
1963 name: "Avengers".to_string(),
1964 };
1965 let lazy = Lazy::loaded(team.clone());
1966 let cloned = lazy.clone();
1967 assert!(cloned.is_loaded());
1968 assert_eq!(cloned.get(), Some(&team));
1969 }
1970
1971 #[test]
1972 fn test_lazy_debug_output_shows_state() {
1973 let lazy = Lazy::<Team>::from_fk(1_i64);
1974 let s = format!("{lazy:?}");
1975 assert!(s.contains("state"));
1976 assert!(s.contains("unloaded"));
1977 }
1978
1979 #[test]
1980 fn test_lazy_serde_serialize_loaded_outputs_object() {
1981 let lazy = Lazy::loaded(Team {
1982 id: Some(1),
1983 name: "Avengers".to_string(),
1984 });
1985 let json = serde_json::to_value(&lazy).unwrap();
1986 assert_eq!(
1987 json,
1988 serde_json::json!({
1989 "id": 1,
1990 "name": "Avengers"
1991 })
1992 );
1993 }
1994
1995 #[test]
1996 fn test_lazy_serde_serialize_unloaded_outputs_null() {
1997 let lazy = Lazy::<Team>::from_fk(1_i64);
1998 let json = serde_json::to_value(&lazy).unwrap();
1999 assert_eq!(json, serde_json::Value::Null);
2000 }
2001
2002 #[test]
2003 fn test_lazy_serde_deserialize_object_creates_loaded() {
2004 let lazy: Lazy<Team> = serde_json::from_value(serde_json::json!({
2005 "id": 1,
2006 "name": "Avengers"
2007 }))
2008 .unwrap();
2009
2010 let expected = Team {
2011 id: Some(1),
2012 name: "Avengers".to_string(),
2013 };
2014 assert!(lazy.is_loaded());
2015 assert_eq!(lazy.get(), Some(&expected));
2016 }
2017
2018 #[test]
2019 fn test_lazy_serde_deserialize_null_creates_empty() {
2020 let lazy: Lazy<Team> = serde_json::from_value(serde_json::Value::Null).unwrap();
2021 assert!(lazy.is_empty());
2022 assert!(!lazy.is_loaded());
2023 assert!(lazy.get().is_none());
2024 }
2025
2026 #[test]
2027 fn test_lazy_serde_roundtrip_preserves_data() {
2028 let lazy = Lazy::loaded(Team {
2029 id: Some(1),
2030 name: "Avengers".to_string(),
2031 });
2032 let json = serde_json::to_string(&lazy).unwrap();
2033 let decoded: Lazy<Team> = serde_json::from_str(&json).unwrap();
2034 assert!(decoded.is_loaded());
2035 assert_eq!(decoded.get(), lazy.get());
2036 }
2037
2038 #[test]
2039 fn test_lazy_reset_clears_loaded_state() {
2040 let mut lazy = Lazy::loaded(Team {
2041 id: Some(1),
2042 name: "Test".to_string(),
2043 });
2044 assert!(lazy.is_loaded());
2045
2046 lazy.reset();
2047 assert!(!lazy.is_loaded());
2048 assert!(lazy.get().is_none());
2049 }
2050
2051 #[test]
2052 fn test_lazy_is_empty_accurate() {
2053 let empty = Lazy::<Team>::empty();
2054 assert!(empty.is_empty());
2055
2056 let with_fk = Lazy::<Team>::from_fk(1_i64);
2057 assert!(!with_fk.is_empty());
2058
2059 let loaded = Lazy::loaded(Team {
2060 id: Some(1),
2061 name: "Test".to_string(),
2062 });
2063 assert!(loaded.is_empty()); }
2065
2066 #[test]
2067 fn test_lazy_load_missing_object_caches_none() {
2068 let lazy = Lazy::<Team>::from_fk(999_i64);
2069 assert!(lazy.set_loaded(None).is_ok());
2071 assert!(lazy.is_loaded());
2072 assert!(lazy.get().is_none());
2073
2074 assert!(lazy.set_loaded(None).is_err());
2076 }
2077
2078 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2083 struct Hero {
2084 id: Option<i64>,
2085 name: String,
2086 }
2087
2088 impl Model for Hero {
2089 const TABLE_NAME: &'static str = "heroes";
2090 const PRIMARY_KEY: &'static [&'static str] = &["id"];
2091 const RELATIONSHIPS: &'static [RelationshipInfo] = &[RelationshipInfo {
2092 name: "team",
2093 related_table: "teams",
2094 kind: RelationshipKind::ManyToOne,
2095 local_key: Some("team_id"),
2096 local_keys: None,
2097 remote_key: None,
2098 remote_keys: None,
2099 link_table: None,
2100 back_populates: Some("heroes"),
2101 lazy: false,
2102 cascade_delete: false,
2103 passive_deletes: PassiveDeletes::Active,
2104 order_by: None,
2105 lazy_strategy: None,
2106 cascade: None,
2107 uselist: None,
2108 related_fields_fn: TeamWithRelationships::fields,
2109 }];
2110
2111 fn fields() -> &'static [FieldInfo] {
2112 &[]
2113 }
2114
2115 fn to_row(&self) -> Vec<(&'static str, Value)> {
2116 vec![]
2117 }
2118
2119 fn from_row(_row: &Row) -> Result<Self> {
2120 Ok(Self {
2121 id: None,
2122 name: String::new(),
2123 })
2124 }
2125
2126 fn primary_key_value(&self) -> Vec<Value> {
2127 match self.id {
2128 Some(id) => vec![Value::from(id)],
2129 None => vec![],
2130 }
2131 }
2132
2133 fn is_new(&self) -> bool {
2134 self.id.is_none()
2135 }
2136 }
2137
2138 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2140 struct TeamWithRelationships {
2141 id: Option<i64>,
2142 name: String,
2143 }
2144
2145 impl Model for TeamWithRelationships {
2146 const TABLE_NAME: &'static str = "teams";
2147 const PRIMARY_KEY: &'static [&'static str] = &["id"];
2148 const RELATIONSHIPS: &'static [RelationshipInfo] = &[RelationshipInfo {
2149 name: "heroes",
2150 related_table: "heroes",
2151 kind: RelationshipKind::OneToMany,
2152 local_key: None,
2153 local_keys: None,
2154 remote_key: Some("team_id"),
2155 remote_keys: None,
2156 link_table: None,
2157 back_populates: Some("team"),
2158 lazy: false,
2159 cascade_delete: false,
2160 passive_deletes: PassiveDeletes::Active,
2161 order_by: None,
2162 lazy_strategy: None,
2163 cascade: None,
2164 uselist: None,
2165 related_fields_fn: Hero::fields,
2166 }];
2167
2168 fn fields() -> &'static [FieldInfo] {
2169 &[]
2170 }
2171
2172 fn to_row(&self) -> Vec<(&'static str, Value)> {
2173 vec![]
2174 }
2175
2176 fn from_row(_row: &Row) -> Result<Self> {
2177 Ok(Self {
2178 id: None,
2179 name: String::new(),
2180 })
2181 }
2182
2183 fn primary_key_value(&self) -> Vec<Value> {
2184 match self.id {
2185 Some(id) => vec![Value::from(id)],
2186 None => vec![],
2187 }
2188 }
2189
2190 fn is_new(&self) -> bool {
2191 self.id.is_none()
2192 }
2193 }
2194
2195 #[test]
2196 fn test_find_relationship_found() {
2197 let rel = find_relationship::<Hero>("team");
2198 assert!(rel.is_some());
2199 let rel = rel.unwrap();
2200 assert_eq!(rel.name, "team");
2201 assert_eq!(rel.related_table, "teams");
2202 assert_eq!(rel.back_populates, Some("heroes"));
2203 }
2204
2205 #[test]
2206 fn test_find_relationship_not_found() {
2207 let rel = find_relationship::<Hero>("powers");
2208 assert!(rel.is_none());
2209 }
2210
2211 #[test]
2212 fn test_find_relationship_empty_relationships() {
2213 let rel = find_relationship::<Team>("heroes");
2215 assert!(rel.is_none());
2216 }
2217
2218 #[test]
2219 fn test_find_back_relationship_found() {
2220 let hero_team_rel = find_relationship::<Hero>("team").unwrap();
2221 let back = find_back_relationship(hero_team_rel, TeamWithRelationships::RELATIONSHIPS);
2222 assert!(back.is_some());
2223 let back = back.unwrap();
2224 assert_eq!(back.name, "heroes");
2225 assert_eq!(back.back_populates, Some("team"));
2226 }
2227
2228 #[test]
2229 fn test_find_back_relationship_no_back_populates() {
2230 let rel = RelationshipInfo::new("team", "teams", RelationshipKind::ManyToOne);
2231 let back = find_back_relationship(&rel, TeamWithRelationships::RELATIONSHIPS);
2232 assert!(back.is_none());
2233 }
2234
2235 #[test]
2236 fn test_validate_back_populates_valid() {
2237 let result = validate_back_populates::<Hero, TeamWithRelationships>("team");
2238 assert!(result.is_ok());
2239 }
2240
2241 #[test]
2242 fn test_validate_back_populates_no_source_relationship() {
2243 let result = validate_back_populates::<Hero, TeamWithRelationships>("nonexistent");
2244 assert!(result.is_err());
2245 assert!(result.unwrap_err().contains("No relationship"));
2246 }
2247
2248 #[test]
2249 fn test_validate_back_populates_no_target_relationship() {
2250 let result = validate_back_populates::<Hero, Team>("team");
2252 assert!(result.is_err());
2253 assert!(result.unwrap_err().contains("does not exist"));
2254 }
2255}