1use crate::EntityPersist;
69use core::ops::{Deref, DerefMut};
70use sql_orm_core::{Entity, EntityMetadata, OrmError, SqlValue};
71use std::any::{Any, TypeId};
72use std::fmt;
73use std::marker::PhantomData;
74use std::sync::{Arc, Mutex};
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum EntityState {
79 Unchanged,
81 Added,
83 Modified,
85 Deleted,
87}
88
89pub struct Tracked<T> {
106 inner: Box<TrackedInner<T>>,
107 registration_id: Option<usize>,
108 tracking_registry: Option<TrackingRegistryHandle>,
109}
110
111#[doc(hidden)]
112#[derive(Debug, Clone, PartialEq, Eq)]
113pub struct TrackedEntityRegistration {
114 pub entry_id: usize,
115 pub entity_rust_name: &'static str,
116 pub state: EntityState,
117}
118
119#[doc(hidden)]
120#[derive(Debug, Default)]
121pub struct TrackingRegistry {
122 state: Mutex<TrackingRegistryState>,
123}
124
125#[doc(hidden)]
126pub type TrackingRegistryHandle = Arc<TrackingRegistry>;
127
128#[doc(hidden)]
129#[derive(Debug, Clone, PartialEq, Eq)]
130pub struct SaveChangesOperationPlan {
131 added_order: Vec<usize>,
132 modified_order: Vec<usize>,
133 deleted_order: Vec<usize>,
134}
135
136struct TrackedInner<T> {
137 original: T,
138 current: T,
139 state: EntityState,
140}
141
142#[derive(Debug, Default)]
143struct TrackingRegistryState {
144 next_registration_id: usize,
145 next_temporary_identity: u64,
146 entries: Vec<TrackingRegistration>,
147}
148
149struct TrackingRegistration {
150 registration_id: usize,
151 identity: TrackedIdentity,
152 entity_type_id: TypeId,
153 entity_rust_name: &'static str,
154 inner_address: usize,
155 wrapper_attached: bool,
156 state: EntityState,
157 snapshots: Box<dyn Any + Send + Sync>,
158 sync_current_from_wrapper: unsafe fn(&mut Box<dyn Any + Send + Sync>, usize),
159}
160
161impl fmt::Debug for TrackingRegistration {
162 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163 f.debug_struct("TrackingRegistration")
164 .field("registration_id", &self.registration_id)
165 .field("identity", &self.identity)
166 .field("entity_type_id", &self.entity_type_id)
167 .field("entity_rust_name", &self.entity_rust_name)
168 .field("inner_address", &self.inner_address)
169 .field("wrapper_attached", &self.wrapper_attached)
170 .field("state", &self.state)
171 .finish_non_exhaustive()
172 }
173}
174
175#[derive(Debug, Clone, PartialEq)]
176struct TrackedIdentity {
177 entity_type_id: TypeId,
178 entity_rust_name: &'static str,
179 schema: &'static str,
180 table: &'static str,
181 primary_key: TrackedPrimaryKeyIdentity,
182}
183
184#[derive(Debug, Clone, PartialEq)]
185enum TrackedPrimaryKeyIdentity {
186 Simple(SqlValue),
187 Temporary(u64),
188}
189
190#[derive(Clone)]
191#[allow(dead_code)]
192struct TrackingSnapshots<E> {
193 original: E,
194 current: E,
195}
196
197#[derive(Clone)]
198pub(crate) struct RegisteredTracked<E> {
199 registration_id: usize,
200 inner_address: usize,
201 tracking_registry: TrackingRegistryHandle,
202 _entity: PhantomData<fn() -> E>,
203}
204
205impl<T: Clone> Tracked<T> {
206 pub fn from_loaded(entity: T) -> Self {
208 Self {
209 inner: Box::new(TrackedInner {
210 original: entity.clone(),
211 current: entity,
212 state: EntityState::Unchanged,
213 }),
214 registration_id: None,
215 tracking_registry: None,
216 }
217 }
218
219 pub fn from_added(entity: T) -> Self {
221 Self {
222 inner: Box::new(TrackedInner {
223 original: entity.clone(),
224 current: entity,
225 state: EntityState::Added,
226 }),
227 registration_id: None,
228 tracking_registry: None,
229 }
230 }
231}
232
233impl<T> Tracked<T> {
234 pub fn original(&self) -> &T {
236 &self.inner.original
237 }
238
239 pub fn current(&self) -> &T {
241 &self.inner.current
242 }
243
244 pub const fn state(&self) -> EntityState {
246 self.inner.state
247 }
248
249 pub fn mark_modified(&mut self) {
255 self.mark_modified_if_unchanged();
256 }
257
258 pub fn mark_deleted(&mut self) {
264 let was_added = self.inner.state == EntityState::Added;
265 self.set_state(EntityState::Deleted);
266 if was_added {
267 self.detach_registry();
268 }
269 }
270
271 pub fn mark_unchanged(&mut self)
277 where
278 T: Clone + Send + Sync + 'static,
279 {
280 self.inner.original = self.inner.current.clone();
281 if let (Some(registration_id), Some(registry)) =
282 (self.registration_id, self.tracking_registry.as_ref())
283 {
284 registry.set_snapshots(
285 registration_id,
286 self.inner.original.clone(),
287 self.inner.current.clone(),
288 );
289 }
290 self.set_state(EntityState::Unchanged);
291 }
292
293 pub fn detach(&mut self) {
298 self.detach_registry();
299 }
300
301 pub fn current_mut(&mut self) -> &mut T {
304 self.mark_modified_if_unchanged();
305 &mut self.inner.current
306 }
307
308 pub(crate) fn current_mut_without_state_change(&mut self) -> &mut T {
309 &mut self.inner.current
310 }
311
312 fn mark_modified_if_unchanged(&mut self) {
313 if self.inner.state == EntityState::Unchanged {
314 self.set_state(EntityState::Modified);
315 }
316 }
317
318 fn set_state(&mut self, state: EntityState) {
319 self.inner.state = state;
320 if let (Some(registration_id), Some(registry)) =
321 (self.registration_id, self.tracking_registry.as_ref())
322 {
323 registry.set_state(registration_id, state);
324 }
325 }
326
327 pub(crate) fn detach_registry(&mut self) {
328 if let (Some(registration_id), Some(registry)) =
329 (self.registration_id.take(), self.tracking_registry.take())
330 {
331 registry.unregister(registration_id);
332 }
333 }
334
335 #[allow(clippy::manual_async_fn)]
344 pub fn save<C>(
345 &mut self,
346 db: &C,
347 ) -> impl core::future::Future<Output = Result<(), OrmError>> + Send
348 where
349 C: crate::DbContextEntitySet<T> + Sync,
350 T: crate::ActiveRecord
351 + crate::AuditEntity
352 + crate::EntityPersist
353 + crate::EntityPrimaryKey
354 + crate::SoftDeleteEntity
355 + crate::TenantScopedEntity
356 + Clone
357 + sql_orm_core::FromRow
358 + Send,
359 {
360 async move {
361 match self.inner.state {
362 EntityState::Unchanged => Ok(()),
363 EntityState::Deleted => Err(OrmError::compile(
364 "tracked deleted entities cannot be saved; detach them or persist deletion",
365 )),
366 EntityState::Added | EntityState::Modified => {
367 crate::ActiveRecord::save(&mut self.inner.current, db).await?;
368 self.inner.original = self.inner.current.clone();
369 self.set_state(EntityState::Unchanged);
370
371 if let (Some(registration_id), Some(registry)) =
372 (self.registration_id, self.tracking_registry.as_ref())
373 {
374 let key =
375 <T as crate::EntityPrimaryKey>::primary_key_value(&self.inner.current)?;
376 registry.update_persisted_identity::<T>(registration_id, key)?;
377 }
378
379 Ok(())
380 }
381 }
382 }
383 }
384
385 #[allow(clippy::manual_async_fn)]
393 pub fn delete<C>(
394 &mut self,
395 db: &C,
396 ) -> impl core::future::Future<Output = Result<bool, OrmError>> + Send
397 where
398 C: crate::DbContextEntitySet<T> + Sync,
399 T: crate::ActiveRecord
400 + crate::EntityPersist
401 + crate::EntityPrimaryKey
402 + crate::SoftDeleteEntity
403 + crate::TenantScopedEntity
404 + Clone
405 + sql_orm_core::FromRow
406 + Send,
407 {
408 async move {
409 match self.inner.state {
410 EntityState::Added => {
411 self.set_state(EntityState::Deleted);
412 self.detach_registry();
413 Ok(false)
414 }
415 EntityState::Deleted => Ok(false),
416 EntityState::Unchanged | EntityState::Modified => {
417 let deleted = crate::ActiveRecord::delete(&self.inner.current, db).await?;
418 if deleted {
419 self.set_state(EntityState::Deleted);
420 self.detach_registry();
421 }
422 Ok(deleted)
423 }
424 }
425 }
426 }
427}
428
429impl<T: Clone> Tracked<T> {
430 pub fn into_current(self) -> T {
432 self.current().clone()
433 }
434}
435
436impl<T: Entity + Clone> Tracked<T> {
437 pub(crate) fn attach_registry_loaded(
438 &mut self,
439 registry: TrackingRegistryHandle,
440 key: SqlValue,
441 ) -> Result<(), OrmError> {
442 let registration_id = registry.register_or_attach_loaded(self, key)?;
443 self.registration_id = Some(registration_id);
444 self.tracking_registry = Some(registry);
445 Ok(())
446 }
447
448 pub(crate) fn attach_registry_added(&mut self, registry: TrackingRegistryHandle) {
449 let registration_id = registry.register_added(self);
450 self.registration_id = Some(registration_id);
451 self.tracking_registry = Some(registry);
452 }
453
454 #[cfg(test)]
455 pub(crate) fn attach_registry(&mut self, registry: TrackingRegistryHandle) {
456 self.attach_registry_added(registry);
457 }
458}
459
460impl<T> Deref for Tracked<T> {
461 type Target = T;
462
463 fn deref(&self) -> &Self::Target {
464 self.current()
465 }
466}
467
468impl<T> DerefMut for Tracked<T> {
469 fn deref_mut(&mut self) -> &mut Self::Target {
470 self.current_mut()
471 }
472}
473
474impl TrackingRegistry {
475 pub(crate) fn register_or_attach_loaded<E: Entity + Clone>(
476 &self,
477 tracked: &mut Tracked<E>,
478 key: SqlValue,
479 ) -> Result<usize, OrmError> {
480 let identity =
481 TrackedIdentity::for_entity::<E>(TrackedPrimaryKeyIdentity::Simple(key.clone()));
482 let mut state = self.state.lock().expect("tracking registry mutex poisoned");
483
484 if let Some(entry) = state
485 .entries
486 .iter_mut()
487 .find(|entry| entry.identity == identity)
488 {
489 if entry.wrapper_attached {
490 return Err(duplicate_live_identity_error::<E>(&key));
491 }
492
493 let Some(snapshots) = entry.snapshots.downcast_ref::<TrackingSnapshots<E>>() else {
494 return Err(OrmError::mapping(format!(
495 "tracked entity `{}` has incompatible registry snapshots",
496 E::metadata().rust_name,
497 )));
498 };
499
500 tracked.inner.original = snapshots.original.clone();
501 tracked.inner.current = snapshots.current.clone();
502 tracked.inner.state = entry.state;
503 entry.inner_address = tracked.inner.as_ref() as *const TrackedInner<E> as usize;
504 entry.wrapper_attached = true;
505 return Ok(entry.registration_id);
506 }
507
508 Ok(state.push_registration(tracked, identity))
509 }
510
511 pub(crate) fn register_added<E: Entity + Clone>(&self, tracked: &Tracked<E>) -> usize {
512 let mut state = self.state.lock().expect("tracking registry mutex poisoned");
513 let temporary_identity = state.next_temporary_identity;
514 state.next_temporary_identity += 1;
515 let identity = TrackedIdentity::for_entity::<E>(TrackedPrimaryKeyIdentity::Temporary(
516 temporary_identity,
517 ));
518 state.push_registration(tracked, identity)
519 }
520
521 pub(crate) fn unregister(&self, registration_id: usize) {
522 let mut state = self.state.lock().expect("tracking registry mutex poisoned");
523 state
524 .entries
525 .retain(|entry| entry.registration_id != registration_id);
526 }
527
528 pub(crate) fn set_state(&self, registration_id: usize, tracked_state: EntityState) {
529 let mut state = self.state.lock().expect("tracking registry mutex poisoned");
530 if let Some(entry) = state
531 .entries
532 .iter_mut()
533 .find(|entry| entry.registration_id == registration_id)
534 {
535 entry.state = tracked_state;
536 }
537 }
538
539 pub(crate) fn detach_wrapper(&self, registration_id: usize) {
540 let mut state = self.state.lock().expect("tracking registry mutex poisoned");
541 if let Some(entry) = state
542 .entries
543 .iter_mut()
544 .find(|entry| entry.registration_id == registration_id)
545 {
546 if entry.wrapper_attached {
547 unsafe {
548 (entry.sync_current_from_wrapper)(&mut entry.snapshots, entry.inner_address);
549 }
550 }
551 entry.wrapper_attached = false;
552 entry.inner_address = 0;
553 }
554 }
555
556 pub(crate) fn set_snapshots<E: Clone + Send + Sync + 'static>(
557 &self,
558 registration_id: usize,
559 original: E,
560 current: E,
561 ) {
562 let mut state = self.state.lock().expect("tracking registry mutex poisoned");
563 if let Some(entry) = state
564 .entries
565 .iter_mut()
566 .find(|entry| entry.registration_id == registration_id)
567 {
568 entry.snapshots = Box::new(TrackingSnapshots::<E> { original, current });
569 }
570 }
571
572 fn sync_current_snapshot_from_wrapper(&self, registration_id: usize) {
573 let mut state = self.state.lock().expect("tracking registry mutex poisoned");
574 if let Some(entry) = state
575 .entries
576 .iter_mut()
577 .find(|entry| entry.registration_id == registration_id)
578 .filter(|entry| entry.wrapper_attached)
579 {
580 unsafe {
581 (entry.sync_current_from_wrapper)(&mut entry.snapshots, entry.inner_address);
582 }
583 }
584 }
585
586 fn is_wrapper_attached(&self, registration_id: usize) -> bool {
587 self.state
588 .lock()
589 .expect("tracking registry mutex poisoned")
590 .entries
591 .iter()
592 .find(|entry| entry.registration_id == registration_id)
593 .is_some_and(|entry| entry.wrapper_attached)
594 }
595
596 fn state_of(&self, registration_id: usize) -> Option<EntityState> {
597 self.state
598 .lock()
599 .expect("tracking registry mutex poisoned")
600 .entries
601 .iter()
602 .find(|entry| entry.registration_id == registration_id)
603 .map(|entry| entry.state)
604 }
605
606 #[allow(dead_code)]
607 fn original_snapshot_of<E: Clone + Send + Sync + 'static>(
608 &self,
609 registration_id: usize,
610 ) -> Option<E> {
611 self.state
612 .lock()
613 .expect("tracking registry mutex poisoned")
614 .entries
615 .iter()
616 .find(|entry| entry.registration_id == registration_id)
617 .and_then(|entry| entry.snapshots.downcast_ref::<TrackingSnapshots<E>>())
618 .map(|snapshots| snapshots.original.clone())
619 }
620
621 #[allow(dead_code)]
622 fn current_snapshot_of<E: Clone + Send + Sync + 'static>(
623 &self,
624 registration_id: usize,
625 ) -> Option<E> {
626 self.state
627 .lock()
628 .expect("tracking registry mutex poisoned")
629 .entries
630 .iter()
631 .find(|entry| entry.registration_id == registration_id)
632 .and_then(|entry| entry.snapshots.downcast_ref::<TrackingSnapshots<E>>())
633 .map(|snapshots| snapshots.current.clone())
634 }
635
636 fn snapshot_pair_of<E: Clone + Send + Sync + 'static>(
637 &self,
638 registration_id: usize,
639 ) -> Option<(E, E)> {
640 self.state
641 .lock()
642 .expect("tracking registry mutex poisoned")
643 .entries
644 .iter()
645 .find(|entry| entry.registration_id == registration_id)
646 .and_then(|entry| entry.snapshots.downcast_ref::<TrackingSnapshots<E>>())
647 .map(|snapshots| (snapshots.original.clone(), snapshots.current.clone()))
648 }
649
650 pub(crate) fn current_snapshot_for_key<E: Entity + Clone + Send + Sync + 'static>(
651 &self,
652 key: SqlValue,
653 ) -> Option<E> {
654 let identity = TrackedIdentity::for_entity::<E>(TrackedPrimaryKeyIdentity::Simple(key));
655 let mut state = self.state.lock().expect("tracking registry mutex poisoned");
656 let entry = state
657 .entries
658 .iter_mut()
659 .find(|entry| entry.identity == identity)?;
660
661 if entry.wrapper_attached {
662 unsafe {
663 (entry.sync_current_from_wrapper)(&mut entry.snapshots, entry.inner_address);
664 }
665 }
666
667 entry
668 .snapshots
669 .downcast_ref::<TrackingSnapshots<E>>()
670 .map(|snapshots| snapshots.current.clone())
671 }
672
673 pub fn clear(&self) {
674 self.state
675 .lock()
676 .expect("tracking registry mutex poisoned")
677 .entries
678 .clear();
679 }
680
681 pub(crate) fn tracked_for<E: Entity>(self: &Arc<Self>) -> Vec<RegisteredTracked<E>> {
682 let state = self.state.lock().expect("tracking registry mutex poisoned");
683
684 state
685 .entries
686 .iter()
687 .filter(|entry| entry.entity_type_id == TypeId::of::<E>())
688 .map(|entry| RegisteredTracked::<E> {
689 registration_id: entry.registration_id,
690 inner_address: entry.inner_address,
691 tracking_registry: Arc::clone(self),
692 _entity: PhantomData,
693 })
694 .collect()
695 }
696
697 pub(crate) fn update_persisted_identity<E: Entity>(
698 &self,
699 registration_id: usize,
700 key: SqlValue,
701 ) -> Result<(), OrmError> {
702 let identity =
703 TrackedIdentity::for_entity::<E>(TrackedPrimaryKeyIdentity::Simple(key.clone()));
704 let mut state = self.state.lock().expect("tracking registry mutex poisoned");
705
706 let target_index = state
707 .entries
708 .iter()
709 .position(|entry| entry.registration_id == registration_id)
710 .ok_or_else(|| OrmError::execution("tracked entity registration was not found"))?;
711
712 if state
713 .entries
714 .iter()
715 .any(|entry| entry.registration_id != registration_id && entry.identity == identity)
716 {
717 return Err(OrmError::compile(format!(
718 "entity `{}` with primary key value `{:?}` is already tracked in this context",
719 E::metadata().rust_name,
720 key
721 )));
722 }
723
724 state.entries[target_index].identity = identity;
725 Ok(())
726 }
727
728 pub fn entry_count(&self) -> usize {
729 self.state
730 .lock()
731 .expect("tracking registry mutex poisoned")
732 .entries
733 .len()
734 }
735
736 pub fn registrations(&self) -> Vec<TrackedEntityRegistration> {
737 self.state
738 .lock()
739 .expect("tracking registry mutex poisoned")
740 .entries
741 .iter()
742 .map(|entry| TrackedEntityRegistration {
743 entry_id: entry.registration_id,
744 entity_rust_name: entry.entity_rust_name,
745 state: entry.state,
746 })
747 .collect()
748 }
749}
750
751#[doc(hidden)]
752pub fn save_changes_operation_plan(
753 entities: &[&'static EntityMetadata],
754) -> Result<SaveChangesOperationPlan, OrmError> {
755 let insert_order = topological_entity_order(entities)?;
756 let mut delete_order = insert_order.clone();
757 delete_order.reverse();
758
759 Ok(SaveChangesOperationPlan {
760 added_order: insert_order.clone(),
761 modified_order: insert_order,
762 deleted_order: delete_order,
763 })
764}
765
766impl SaveChangesOperationPlan {
767 pub fn added_order(&self) -> &[usize] {
768 &self.added_order
769 }
770
771 pub fn modified_order(&self) -> &[usize] {
772 &self.modified_order
773 }
774
775 pub fn deleted_order(&self) -> &[usize] {
776 &self.deleted_order
777 }
778}
779
780fn topological_entity_order(entities: &[&'static EntityMetadata]) -> Result<Vec<usize>, OrmError> {
781 let mut outgoing_edges = vec![Vec::<usize>::new(); entities.len()];
782 let mut incoming_edge_count = vec![0usize; entities.len()];
783
784 for (child_index, child) in entities.iter().enumerate() {
785 for foreign_key in child.foreign_keys {
786 if foreign_key.columns.len() != 1 || foreign_key.referenced_columns.len() != 1 {
787 continue;
788 }
789
790 let Some(parent_index) = entities.iter().position(|candidate| {
791 candidate.schema == foreign_key.referenced_schema
792 && candidate.table == foreign_key.referenced_table
793 }) else {
794 continue;
795 };
796
797 if parent_index == child_index || outgoing_edges[parent_index].contains(&child_index) {
798 continue;
799 }
800
801 outgoing_edges[parent_index].push(child_index);
802 incoming_edge_count[child_index] += 1;
803 }
804 }
805
806 let mut order = Vec::with_capacity(entities.len());
807 let mut ready: Vec<usize> = incoming_edge_count
808 .iter()
809 .enumerate()
810 .filter_map(|(index, count)| (*count == 0).then_some(index))
811 .collect();
812
813 while !ready.is_empty() {
814 ready.sort_unstable();
815 let entity_index = ready.remove(0);
816 order.push(entity_index);
817
818 for child_index in &outgoing_edges[entity_index] {
819 incoming_edge_count[*child_index] -= 1;
820 if incoming_edge_count[*child_index] == 0 {
821 ready.push(*child_index);
822 }
823 }
824 }
825
826 if order.len() != entities.len() {
827 return Err(OrmError::compile(
828 "save_changes cannot determine a deterministic order for tracked operations because the context contains a foreign-key cycle",
829 ));
830 }
831
832 Ok(order)
833}
834
835impl TrackingRegistryState {
836 fn push_registration<E: Entity + Clone>(
837 &mut self,
838 tracked: &Tracked<E>,
839 identity: TrackedIdentity,
840 ) -> usize {
841 let registration_id = self.next_registration_id;
842 self.next_registration_id += 1;
843 self.entries.push(TrackingRegistration {
844 registration_id,
845 identity,
846 entity_type_id: TypeId::of::<E>(),
847 entity_rust_name: E::metadata().rust_name,
848 inner_address: tracked.inner.as_ref() as *const TrackedInner<E> as usize,
849 wrapper_attached: true,
850 state: tracked.inner.state,
851 snapshots: Box::new(TrackingSnapshots::<E> {
852 original: tracked.inner.original.clone(),
853 current: tracked.inner.current.clone(),
854 }),
855 sync_current_from_wrapper: sync_current_snapshot_from_wrapper::<E>,
856 });
857 registration_id
858 }
859}
860
861impl TrackedIdentity {
862 fn for_entity<E: Entity>(primary_key: TrackedPrimaryKeyIdentity) -> Self {
863 let metadata = E::metadata();
864 Self {
865 entity_type_id: TypeId::of::<E>(),
866 entity_rust_name: metadata.rust_name,
867 schema: metadata.schema,
868 table: metadata.table,
869 primary_key,
870 }
871 }
872}
873
874impl<E: Clone + Send + Sync + 'static> RegisteredTracked<E> {
875 pub(crate) fn registration_id(&self) -> usize {
876 self.registration_id
877 }
878
879 pub(crate) fn state(&self) -> EntityState {
880 self.tracking_registry
881 .state_of(self.registration_id)
882 .unwrap_or_else(|| unsafe { (&*(self.inner_address as *const TrackedInner<E>)).state })
883 }
884
885 pub(crate) fn current_clone(&self) -> E {
886 self.sync_current_snapshot_from_wrapper();
887 self.tracking_registry
888 .current_snapshot_of::<E>(self.registration_id)
889 .unwrap_or_else(|| unsafe {
890 (&*(self.inner_address as *const TrackedInner<E>))
891 .current
892 .clone()
893 })
894 }
895
896 fn sync_current_snapshot_from_wrapper(&self) {
897 self.tracking_registry
898 .sync_current_snapshot_from_wrapper(self.registration_id);
899 }
900
901 pub(crate) fn accept_current(&self) {
902 let current = self.current_clone();
903 if self
904 .tracking_registry
905 .is_wrapper_attached(self.registration_id)
906 {
907 unsafe {
908 let inner = self.inner_address as *mut TrackedInner<E>;
909 (*inner).original = current.clone();
910 (*inner).state = EntityState::Unchanged;
911 }
912 }
913 self.tracking_registry
914 .set_snapshots(self.registration_id, current.clone(), current);
915 self.tracking_registry
916 .set_state(self.registration_id, EntityState::Unchanged);
917 }
918
919 pub(crate) fn sync_persisted(&self, persisted: E) {
920 let snapshot = persisted.clone();
921 if self
922 .tracking_registry
923 .is_wrapper_attached(self.registration_id)
924 {
925 unsafe {
926 let inner = self.inner_address as *mut TrackedInner<E>;
927 (*inner).original = persisted.clone();
928 (*inner).current = persisted;
929 (*inner).state = EntityState::Unchanged;
930 }
931 }
932 self.tracking_registry
933 .set_snapshots(self.registration_id, snapshot.clone(), snapshot);
934 self.tracking_registry
935 .set_state(self.registration_id, EntityState::Unchanged);
936 }
937}
938
939impl<E: EntityPersist + Clone + Send + Sync + 'static> RegisteredTracked<E> {
940 pub(crate) fn has_persisted_changes(&self) -> bool {
941 self.sync_current_snapshot_from_wrapper();
942 self.tracking_registry
943 .snapshot_pair_of::<E>(self.registration_id)
944 .map(|(original, current)| E::has_persisted_changes(&original, ¤t))
945 .unwrap_or_else(|| unsafe {
946 let inner = &*(self.inner_address as *const TrackedInner<E>);
947 E::has_persisted_changes(&inner.original, &inner.current)
948 })
949 }
950}
951
952fn duplicate_live_identity_error<E: Entity>(key: &SqlValue) -> OrmError {
953 OrmError::compile(format!(
954 "entity `{}` with primary key value `{:?}` already has a live tracked handle in this context; detach or drop the existing handle before loading it again",
955 E::metadata().rust_name,
956 key
957 ))
958}
959
960unsafe fn sync_current_snapshot_from_wrapper<E: Clone + Send + Sync + 'static>(
961 snapshots: &mut Box<dyn Any + Send + Sync>,
962 inner_address: usize,
963) {
964 let Some(snapshots) = snapshots.downcast_mut::<TrackingSnapshots<E>>() else {
965 return;
966 };
967 let inner = unsafe { &*(inner_address as *const TrackedInner<E>) };
968 snapshots.current = inner.current.clone();
969}
970
971impl<T: Clone> Clone for Tracked<T> {
972 fn clone(&self) -> Self {
973 Self {
974 inner: Box::new(TrackedInner {
975 original: self.original().clone(),
976 current: self.current().clone(),
977 state: self.state(),
978 }),
979 registration_id: None,
980 tracking_registry: None,
981 }
982 }
983}
984
985impl<T: core::fmt::Debug> core::fmt::Debug for Tracked<T> {
986 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
987 f.debug_struct("Tracked")
988 .field("original", self.original())
989 .field("current", self.current())
990 .field("state", &self.state())
991 .finish()
992 }
993}
994
995impl<T: PartialEq> PartialEq for Tracked<T> {
996 fn eq(&self, other: &Self) -> bool {
997 self.original() == other.original()
998 && self.current() == other.current()
999 && self.state() == other.state()
1000 }
1001}
1002
1003impl<T: Eq> Eq for Tracked<T> {}
1004
1005impl<T> Drop for Tracked<T> {
1006 fn drop(&mut self) {
1007 if let (Some(registration_id), Some(registry)) =
1008 (self.registration_id.take(), self.tracking_registry.take())
1009 {
1010 if matches!(
1011 self.inner.state,
1012 EntityState::Added | EntityState::Modified | EntityState::Deleted
1013 ) {
1014 registry.detach_wrapper(registration_id);
1015 } else {
1016 registry.unregister(registration_id);
1017 }
1018 }
1019 }
1020}
1021
1022#[cfg(test)]
1023mod tests {
1024 use super::{
1025 EntityState, Tracked, TrackedEntityRegistration, TrackingRegistry,
1026 save_changes_operation_plan,
1027 };
1028 use crate::{EntityPersist, EntityPersistMode};
1029 use sql_orm_core::{
1030 ColumnValue, Entity, EntityMetadata, ForeignKeyMetadata, OrmError, OrmErrorKind,
1031 PrimaryKeyMetadata, ReferentialAction, SqlValue,
1032 };
1033 use std::sync::Arc;
1034
1035 #[derive(Clone)]
1036 struct DummyEntity;
1037
1038 #[derive(Clone)]
1039 struct DummyEntityAlias;
1040
1041 #[derive(Clone, Debug, PartialEq, Eq)]
1042 struct SnapshotEntity {
1043 name: String,
1044 }
1045
1046 #[derive(Clone, Debug, PartialEq, Eq)]
1047 struct SnapshotEntityAlias {
1048 name: String,
1049 }
1050
1051 static DUMMY_ENTITY_METADATA: EntityMetadata = EntityMetadata {
1052 rust_name: "DummyEntity",
1053 schema: "dbo",
1054 table: "dummy_entities",
1055 renamed_from: None,
1056 columns: &[],
1057 primary_key: PrimaryKeyMetadata {
1058 name: None,
1059 columns: &[],
1060 },
1061 indexes: &[],
1062 foreign_keys: &[],
1063 navigations: &[],
1064 };
1065
1066 static ORDER_METADATA: EntityMetadata = EntityMetadata {
1067 rust_name: "Order",
1068 schema: "sales",
1069 table: "orders",
1070 renamed_from: None,
1071 columns: &[],
1072 primary_key: PrimaryKeyMetadata {
1073 name: None,
1074 columns: &["id"],
1075 },
1076 indexes: &[],
1077 foreign_keys: &[],
1078 navigations: &[],
1079 };
1080
1081 static ORDER_ITEM_FOREIGN_KEYS: [ForeignKeyMetadata; 1] = [ForeignKeyMetadata::new(
1082 "fk_order_items_orders",
1083 &["order_id"],
1084 "sales",
1085 "orders",
1086 &["id"],
1087 ReferentialAction::NoAction,
1088 ReferentialAction::NoAction,
1089 )];
1090
1091 static ORDER_ITEM_METADATA: EntityMetadata = EntityMetadata {
1092 rust_name: "OrderItem",
1093 schema: "sales",
1094 table: "order_items",
1095 renamed_from: None,
1096 columns: &[],
1097 primary_key: PrimaryKeyMetadata {
1098 name: None,
1099 columns: &["id"],
1100 },
1101 indexes: &[],
1102 foreign_keys: &ORDER_ITEM_FOREIGN_KEYS,
1103 navigations: &[],
1104 };
1105
1106 static CATEGORY_FOREIGN_KEYS: [ForeignKeyMetadata; 1] = [ForeignKeyMetadata::new(
1107 "fk_categories_parent",
1108 &["parent_id"],
1109 "catalog",
1110 "categories",
1111 &["id"],
1112 ReferentialAction::NoAction,
1113 ReferentialAction::NoAction,
1114 )];
1115
1116 static CATEGORY_METADATA: EntityMetadata = EntityMetadata {
1117 rust_name: "Category",
1118 schema: "catalog",
1119 table: "categories",
1120 renamed_from: None,
1121 columns: &[],
1122 primary_key: PrimaryKeyMetadata {
1123 name: None,
1124 columns: &["id"],
1125 },
1126 indexes: &[],
1127 foreign_keys: &CATEGORY_FOREIGN_KEYS,
1128 navigations: &[],
1129 };
1130
1131 static CYCLE_A_FOREIGN_KEYS: [ForeignKeyMetadata; 1] = [ForeignKeyMetadata::new(
1132 "fk_cycle_a_cycle_b",
1133 &["cycle_b_id"],
1134 "dbo",
1135 "cycle_b",
1136 &["id"],
1137 ReferentialAction::NoAction,
1138 ReferentialAction::NoAction,
1139 )];
1140
1141 static CYCLE_B_FOREIGN_KEYS: [ForeignKeyMetadata; 1] = [ForeignKeyMetadata::new(
1142 "fk_cycle_b_cycle_a",
1143 &["cycle_a_id"],
1144 "dbo",
1145 "cycle_a",
1146 &["id"],
1147 ReferentialAction::NoAction,
1148 ReferentialAction::NoAction,
1149 )];
1150
1151 static CYCLE_A_METADATA: EntityMetadata = EntityMetadata {
1152 rust_name: "CycleA",
1153 schema: "dbo",
1154 table: "cycle_a",
1155 renamed_from: None,
1156 columns: &[],
1157 primary_key: PrimaryKeyMetadata {
1158 name: None,
1159 columns: &["id"],
1160 },
1161 indexes: &[],
1162 foreign_keys: &CYCLE_A_FOREIGN_KEYS,
1163 navigations: &[],
1164 };
1165
1166 static CYCLE_B_METADATA: EntityMetadata = EntityMetadata {
1167 rust_name: "CycleB",
1168 schema: "dbo",
1169 table: "cycle_b",
1170 renamed_from: None,
1171 columns: &[],
1172 primary_key: PrimaryKeyMetadata {
1173 name: None,
1174 columns: &["id"],
1175 },
1176 indexes: &[],
1177 foreign_keys: &CYCLE_B_FOREIGN_KEYS,
1178 navigations: &[],
1179 };
1180
1181 impl Entity for DummyEntity {
1182 fn metadata() -> &'static EntityMetadata {
1183 &DUMMY_ENTITY_METADATA
1184 }
1185 }
1186
1187 impl Entity for DummyEntityAlias {
1188 fn metadata() -> &'static EntityMetadata {
1189 &DUMMY_ENTITY_METADATA
1190 }
1191 }
1192
1193 impl Entity for SnapshotEntity {
1194 fn metadata() -> &'static EntityMetadata {
1195 &DUMMY_ENTITY_METADATA
1196 }
1197 }
1198
1199 impl Entity for SnapshotEntityAlias {
1200 fn metadata() -> &'static EntityMetadata {
1201 &DUMMY_ENTITY_METADATA
1202 }
1203 }
1204
1205 impl EntityPersist for SnapshotEntity {
1206 fn persist_mode(&self) -> Result<EntityPersistMode, OrmError> {
1207 Ok(EntityPersistMode::Update(SqlValue::I64(1)))
1208 }
1209
1210 fn insert_values(&self) -> Vec<ColumnValue> {
1211 Vec::new()
1212 }
1213
1214 fn update_changes(&self) -> Vec<ColumnValue> {
1215 vec![ColumnValue::new(
1216 "name",
1217 SqlValue::String(self.name.clone()),
1218 )]
1219 }
1220
1221 fn concurrency_token(&self) -> Result<Option<SqlValue>, OrmError> {
1222 Ok(None)
1223 }
1224
1225 fn sync_persisted(&mut self, persisted: Self) {
1226 *self = persisted;
1227 }
1228 }
1229
1230 #[test]
1231 fn tracked_loaded_value_keeps_original_and_current_snapshots() {
1232 let tracked = Tracked::from_loaded(String::from("Ana"));
1233
1234 assert_eq!(tracked.state(), EntityState::Unchanged);
1235 assert_eq!(tracked.original(), "Ana");
1236 assert_eq!(tracked.current(), "Ana");
1237 }
1238
1239 #[test]
1240 fn tracked_added_value_starts_in_added_state() {
1241 let tracked = Tracked::from_added(String::from("Luis"));
1242
1243 assert_eq!(tracked.state(), EntityState::Added);
1244 assert_eq!(tracked.original(), "Luis");
1245 assert_eq!(tracked.current(), "Luis");
1246 }
1247
1248 #[test]
1249 fn tracked_can_release_current_value() {
1250 let tracked = Tracked::from_loaded(String::from("Maria"));
1251
1252 assert_eq!(tracked.into_current(), "Maria");
1253 }
1254
1255 #[test]
1256 fn into_current_consumes_registered_wrapper_and_unregisters_it() {
1257 let registry = Arc::new(TrackingRegistry::default());
1258 let mut tracked = Tracked::from_loaded(DummyEntity);
1259 tracked.attach_registry(Arc::clone(®istry));
1260
1261 assert_eq!(registry.entry_count(), 1);
1262
1263 let _current = tracked.into_current();
1264
1265 assert_eq!(registry.entry_count(), 0);
1266 }
1267
1268 #[test]
1269 fn cloned_tracked_wrapper_is_detached_from_original_registry_entry() {
1270 let registry = Arc::new(TrackingRegistry::default());
1271 let mut original = Tracked::from_loaded(DummyEntity);
1272 original.attach_registry(Arc::clone(®istry));
1273 original.mark_modified();
1274
1275 let clone = original.clone();
1276
1277 assert_eq!(registry.entry_count(), 1);
1278 assert_eq!(clone.state(), EntityState::Modified);
1279
1280 drop(clone);
1281
1282 assert_eq!(registry.entry_count(), 1);
1283 assert_eq!(registry.registrations()[0].state, EntityState::Modified);
1284 }
1285
1286 #[test]
1287 fn mutable_access_transitions_loaded_entity_to_modified() {
1288 let mut tracked = Tracked::from_loaded(String::from("Ana"));
1289
1290 tracked.push_str(" Maria");
1291
1292 assert_eq!(tracked.state(), EntityState::Modified);
1293 assert_eq!(tracked.original(), "Ana");
1294 assert_eq!(tracked.current(), "Ana Maria");
1295 }
1296
1297 #[test]
1298 fn current_mut_transitions_loaded_entity_to_modified() {
1299 let mut tracked = Tracked::from_loaded(String::from("Luis"));
1300
1301 tracked.current_mut().push_str(" Alberto");
1302
1303 assert_eq!(tracked.state(), EntityState::Modified);
1304 assert_eq!(tracked.original(), "Luis");
1305 assert_eq!(tracked.current(), "Luis Alberto");
1306 }
1307
1308 #[test]
1309 fn explicit_mark_modified_transitions_unchanged_only() {
1310 let mut loaded = Tracked::from_loaded(String::from("Ana"));
1311 loaded.mark_modified();
1312
1313 let mut added = Tracked::from_added(String::from("Luis"));
1314 added.mark_modified();
1315
1316 let mut deleted = Tracked::from_loaded(String::from("Maria"));
1317 deleted.mark_deleted();
1318 deleted.mark_modified();
1319
1320 assert_eq!(loaded.state(), EntityState::Modified);
1321 assert_eq!(added.state(), EntityState::Added);
1322 assert_eq!(deleted.state(), EntityState::Deleted);
1323 }
1324
1325 #[test]
1326 fn explicit_mark_deleted_transitions_wrapper_to_deleted() {
1327 let mut tracked = Tracked::from_loaded(String::from("Ana"));
1328
1329 tracked.mark_deleted();
1330
1331 assert_eq!(tracked.state(), EntityState::Deleted);
1332 }
1333
1334 #[test]
1335 fn explicit_mark_unchanged_accepts_current_snapshot() {
1336 let mut tracked = Tracked::from_loaded(String::from("Ana"));
1337 tracked.current_mut().push_str(" Maria");
1338
1339 tracked.mark_unchanged();
1340
1341 assert_eq!(tracked.state(), EntityState::Unchanged);
1342 assert_eq!(tracked.original(), "Ana Maria");
1343 assert_eq!(tracked.current(), "Ana Maria");
1344 }
1345
1346 #[test]
1347 fn explicit_mark_unchanged_restores_deleted_wrapper_with_current_snapshot() {
1348 let mut tracked = Tracked::from_loaded(String::from("Ana"));
1349 tracked.current_mut().push_str(" Maria");
1350 tracked.mark_deleted();
1351
1352 tracked.mark_unchanged();
1353
1354 assert_eq!(tracked.state(), EntityState::Unchanged);
1355 assert_eq!(tracked.original(), "Ana Maria");
1356 assert_eq!(tracked.current(), "Ana Maria");
1357 }
1358
1359 #[test]
1360 fn explicit_mark_unchanged_on_registered_wrapper_updates_registry_state() {
1361 let registry = Arc::new(TrackingRegistry::default());
1362 let mut tracked = Tracked::from_loaded(DummyEntity);
1363 tracked.attach_registry(Arc::clone(®istry));
1364 tracked.mark_deleted();
1365
1366 tracked.mark_unchanged();
1367
1368 assert_eq!(tracked.state(), EntityState::Unchanged);
1369 assert_eq!(registry.entry_count(), 1);
1370 assert_eq!(registry.registrations()[0].state, EntityState::Unchanged);
1371 }
1372
1373 #[test]
1374 fn mark_deleted_transitions_any_registered_entity_to_deleted() {
1375 let registry = Arc::new(TrackingRegistry::default());
1376 let mut tracked = Tracked::from_loaded(DummyEntity);
1377 tracked.attach_registry(Arc::clone(®istry));
1378
1379 tracked.mark_deleted();
1380
1381 assert_eq!(tracked.state(), EntityState::Deleted);
1382 assert_eq!(registry.registrations()[0].state, EntityState::Deleted);
1383 }
1384
1385 #[test]
1386 fn mark_deleted_on_added_registered_entry_cancels_pending_insert() {
1387 let registry = Arc::new(TrackingRegistry::default());
1388 let mut tracked = Tracked::from_added(DummyEntity);
1389 tracked.attach_registry_added(Arc::clone(®istry));
1390
1391 tracked.mark_deleted();
1392
1393 assert_eq!(tracked.state(), EntityState::Deleted);
1394 assert_eq!(registry.entry_count(), 0);
1395 }
1396
1397 #[test]
1398 fn mutable_access_keeps_added_state_for_new_entities() {
1399 let mut tracked = Tracked::from_added(String::from("Maria"));
1400
1401 tracked.push_str(" Fernanda");
1402
1403 assert_eq!(tracked.state(), EntityState::Added);
1404 assert_eq!(tracked.original(), "Maria");
1405 assert_eq!(tracked.current(), "Maria Fernanda");
1406 }
1407
1408 #[test]
1409 fn tracking_registry_records_loaded_entities() {
1410 let registry = Arc::new(TrackingRegistry::default());
1411 let mut tracked = Tracked::from_loaded(DummyEntity);
1412
1413 tracked
1414 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(7))
1415 .unwrap();
1416
1417 assert_eq!(registry.entry_count(), 1);
1418 assert_eq!(
1419 registry.registrations(),
1420 vec![TrackedEntityRegistration {
1421 entry_id: 0,
1422 entity_rust_name: "DummyEntity",
1423 state: EntityState::Unchanged,
1424 }]
1425 );
1426 }
1427
1428 #[test]
1429 fn tracking_registry_records_added_entities() {
1430 let registry = Arc::new(TrackingRegistry::default());
1431 let mut tracked = Tracked::from_added(DummyEntity);
1432
1433 tracked.attach_registry(Arc::clone(®istry));
1434
1435 assert_eq!(registry.entry_count(), 1);
1436 assert_eq!(
1437 registry.registrations(),
1438 vec![TrackedEntityRegistration {
1439 entry_id: 0,
1440 entity_rust_name: "DummyEntity",
1441 state: EntityState::Added,
1442 }]
1443 );
1444 }
1445
1446 #[test]
1447 fn tracking_registry_diagnostics_expose_stable_entry_ids() {
1448 let registry = Arc::new(TrackingRegistry::default());
1449 let mut first = Tracked::from_added(DummyEntity);
1450 let mut second = Tracked::from_added(DummyEntity);
1451
1452 first.attach_registry_added(Arc::clone(®istry));
1453 second.attach_registry_added(Arc::clone(®istry));
1454
1455 let registrations = registry.registrations();
1456
1457 assert_eq!(registrations.len(), 2);
1458 assert_eq!(registrations[0].entry_id, 0);
1459 assert_eq!(registrations[1].entry_id, 1);
1460 assert_eq!(registrations[0].entity_rust_name, "DummyEntity");
1461 assert_eq!(registrations[1].entity_rust_name, "DummyEntity");
1462 }
1463
1464 #[test]
1465 fn tracking_registry_diagnostic_entry_ids_are_not_reused_after_unregister() {
1466 let registry = Arc::new(TrackingRegistry::default());
1467 let mut first = Tracked::from_added(DummyEntity);
1468 let mut second = Tracked::from_added(DummyEntity);
1469
1470 first.attach_registry_added(Arc::clone(®istry));
1471 let first_registration_id = first.registration_id.expect("registered first entity");
1472 registry.unregister(first_registration_id);
1473 second.attach_registry_added(Arc::clone(®istry));
1474
1475 let registrations = registry.registrations();
1476
1477 assert_eq!(registrations.len(), 1);
1478 assert_eq!(registrations[0].entry_id, 1);
1479 assert_eq!(registrations[0].state, EntityState::Added);
1480 }
1481
1482 #[test]
1483 fn tracking_registry_diagnostic_entry_ids_are_not_reused_after_clear() {
1484 let registry = Arc::new(TrackingRegistry::default());
1485 let mut first = Tracked::from_added(DummyEntity);
1486 let mut second = Tracked::from_added(DummyEntity);
1487 let mut third = Tracked::from_added(DummyEntity);
1488
1489 first.attach_registry_added(Arc::clone(®istry));
1490 second.attach_registry_added(Arc::clone(®istry));
1491 registry.clear();
1492 third.attach_registry_added(Arc::clone(®istry));
1493
1494 let registrations = registry.registrations();
1495
1496 assert_eq!(registrations.len(), 1);
1497 assert_eq!(registrations[0].entry_id, 2);
1498 assert_eq!(registrations[0].state, EntityState::Added);
1499 }
1500
1501 #[test]
1502 fn tracking_registry_owns_observable_state_for_registered_entries() {
1503 let registry = Arc::new(TrackingRegistry::default());
1504 let mut tracked = Tracked::from_loaded(DummyEntity);
1505 tracked.attach_registry(Arc::clone(®istry));
1506
1507 tracked.inner.state = EntityState::Deleted;
1508
1509 assert_eq!(tracked.state(), EntityState::Deleted);
1510 assert_eq!(registry.registrations()[0].state, EntityState::Unchanged);
1511 assert_eq!(
1512 registry.tracked_for::<DummyEntity>()[0].state(),
1513 EntityState::Unchanged
1514 );
1515
1516 tracked.mark_unchanged();
1517
1518 assert_eq!(tracked.state(), EntityState::Unchanged);
1519 assert_eq!(registry.registrations()[0].state, EntityState::Unchanged);
1520 }
1521
1522 #[test]
1523 fn tracking_registry_owns_initial_snapshots_for_registered_entries() {
1524 let registry = Arc::new(TrackingRegistry::default());
1525 let mut tracked = Tracked::from_loaded(SnapshotEntity {
1526 name: "loaded".to_string(),
1527 });
1528 tracked.attach_registry(Arc::clone(®istry));
1529 let registration_id = tracked.registration_id.expect("registered");
1530
1531 tracked.inner.original.name = "wrapper original changed".to_string();
1532 tracked.inner.current.name = "wrapper current changed".to_string();
1533
1534 assert_eq!(
1535 registry
1536 .original_snapshot_of::<SnapshotEntity>(registration_id)
1537 .unwrap()
1538 .name,
1539 "loaded"
1540 );
1541 assert_eq!(
1542 registry
1543 .current_snapshot_of::<SnapshotEntity>(registration_id)
1544 .unwrap()
1545 .name,
1546 "loaded"
1547 );
1548 }
1549
1550 #[test]
1551 fn mark_unchanged_syncs_registry_owned_snapshots() {
1552 let registry = Arc::new(TrackingRegistry::default());
1553 let mut tracked = Tracked::from_loaded(SnapshotEntity {
1554 name: "loaded".to_string(),
1555 });
1556 tracked.attach_registry(Arc::clone(®istry));
1557 let registration_id = tracked.registration_id.expect("registered");
1558
1559 tracked.current_mut().name = "accepted".to_string();
1560 tracked.mark_unchanged();
1561
1562 assert_eq!(
1563 registry
1564 .original_snapshot_of::<SnapshotEntity>(registration_id)
1565 .unwrap()
1566 .name,
1567 "accepted"
1568 );
1569 assert_eq!(
1570 registry
1571 .current_snapshot_of::<SnapshotEntity>(registration_id)
1572 .unwrap()
1573 .name,
1574 "accepted"
1575 );
1576 }
1577
1578 #[test]
1579 fn registered_tracked_helpers_read_snapshots_from_registry() {
1580 let registry = Arc::new(TrackingRegistry::default());
1581 let mut tracked = Tracked::from_loaded(SnapshotEntity {
1582 name: "loaded".to_string(),
1583 });
1584 tracked.attach_registry(Arc::clone(®istry));
1585 let registration_id = tracked.registration_id.expect("registered");
1586
1587 tracked.inner.original.name = "wrapper original changed".to_string();
1588 tracked.inner.current.name = "changed".to_string();
1589
1590 let registered = registry.tracked_for::<SnapshotEntity>()[0].clone();
1591
1592 assert!(registered.has_persisted_changes());
1593 assert_eq!(registered.current_clone().name, "changed");
1594 assert_eq!(
1595 registry
1596 .original_snapshot_of::<SnapshotEntity>(registration_id)
1597 .unwrap()
1598 .name,
1599 "loaded"
1600 );
1601 assert_eq!(
1602 registry
1603 .current_snapshot_of::<SnapshotEntity>(registration_id)
1604 .unwrap()
1605 .name,
1606 "changed"
1607 );
1608 }
1609
1610 #[test]
1611 fn registered_tracked_sync_persisted_updates_detached_registry_owned_snapshots() {
1612 let registry = Arc::new(TrackingRegistry::default());
1613
1614 {
1615 let mut tracked = Tracked::from_loaded(SnapshotEntity {
1616 name: "loaded".to_string(),
1617 });
1618 tracked
1619 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(7))
1620 .unwrap();
1621 tracked.current_mut().name = "changed before drop".to_string();
1622 }
1623
1624 let registered = registry.tracked_for::<SnapshotEntity>()[0].clone();
1625 registered.sync_persisted(SnapshotEntity {
1626 name: "persisted value".to_string(),
1627 });
1628
1629 assert_eq!(registry.entry_count(), 1);
1630 assert_eq!(registry.registrations()[0].state, EntityState::Unchanged);
1631 assert!(!registered.has_persisted_changes());
1632 assert_eq!(registered.current_clone().name, "persisted value");
1633
1634 let mut reattached = Tracked::from_loaded(SnapshotEntity {
1635 name: "stale database value".to_string(),
1636 });
1637 reattached
1638 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(7))
1639 .unwrap();
1640
1641 assert_eq!(reattached.state(), EntityState::Unchanged);
1642 assert_eq!(reattached.original().name, "persisted value");
1643 assert_eq!(reattached.current().name, "persisted value");
1644 }
1645
1646 #[test]
1647 fn registered_tracked_accept_current_updates_detached_registry_owned_snapshots() {
1648 let registry = Arc::new(TrackingRegistry::default());
1649
1650 {
1651 let mut tracked = Tracked::from_loaded(SnapshotEntity {
1652 name: "loaded".to_string(),
1653 });
1654 tracked
1655 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(7))
1656 .unwrap();
1657 tracked.current_mut().name = "accepted detached current".to_string();
1658 }
1659
1660 let registered = registry.tracked_for::<SnapshotEntity>()[0].clone();
1661 assert!(registered.has_persisted_changes());
1662
1663 registered.accept_current();
1664
1665 assert_eq!(registry.entry_count(), 1);
1666 assert_eq!(registry.registrations()[0].state, EntityState::Unchanged);
1667 assert!(!registered.has_persisted_changes());
1668 assert_eq!(registered.current_clone().name, "accepted detached current");
1669
1670 let mut reattached = Tracked::from_loaded(SnapshotEntity {
1671 name: "stale database value".to_string(),
1672 });
1673 reattached
1674 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(7))
1675 .unwrap();
1676
1677 assert_eq!(reattached.state(), EntityState::Unchanged);
1678 assert_eq!(reattached.original().name, "accepted detached current");
1679 assert_eq!(reattached.current().name, "accepted detached current");
1680 }
1681
1682 #[test]
1683 fn dropping_added_wrapper_detaches_handle_without_removing_registry_entry() {
1684 let registry = Arc::new(TrackingRegistry::default());
1685
1686 {
1687 let mut tracked = Tracked::from_added(SnapshotEntity {
1688 name: "new".to_string(),
1689 });
1690 tracked.attach_registry_added(Arc::clone(®istry));
1691 tracked.current_mut().name = "changed before drop".to_string();
1692
1693 assert_eq!(registry.entry_count(), 1);
1694 }
1695
1696 let registered = registry.tracked_for::<SnapshotEntity>()[0].clone();
1697
1698 assert_eq!(registry.entry_count(), 1);
1699 assert_eq!(registry.registrations()[0].state, EntityState::Added);
1700 assert_eq!(registered.current_clone().name, "changed before drop");
1701 }
1702
1703 #[test]
1704 fn dropping_modified_wrapper_detaches_handle_without_removing_registry_entry() {
1705 let registry = Arc::new(TrackingRegistry::default());
1706
1707 {
1708 let mut tracked = Tracked::from_loaded(SnapshotEntity {
1709 name: "loaded".to_string(),
1710 });
1711 tracked.attach_registry(Arc::clone(®istry));
1712 tracked.current_mut().name = "changed before drop".to_string();
1713
1714 assert_eq!(registry.entry_count(), 1);
1715 assert_eq!(registry.registrations()[0].state, EntityState::Modified);
1716 }
1717
1718 let registered = registry.tracked_for::<SnapshotEntity>()[0].clone();
1719
1720 assert_eq!(registry.entry_count(), 1);
1721 assert_eq!(registry.registrations()[0].state, EntityState::Modified);
1722 assert_eq!(registered.current_clone().name, "changed before drop");
1723 assert!(registered.has_persisted_changes());
1724 }
1725
1726 #[test]
1727 fn dropping_deleted_wrapper_detaches_handle_without_removing_registry_entry() {
1728 let registry = Arc::new(TrackingRegistry::default());
1729
1730 {
1731 let mut tracked = Tracked::from_loaded(SnapshotEntity {
1732 name: "loaded".to_string(),
1733 });
1734 tracked.attach_registry(Arc::clone(®istry));
1735 tracked.current_mut().name = "changed before delete".to_string();
1736 tracked.mark_deleted();
1737
1738 assert_eq!(registry.entry_count(), 1);
1739 assert_eq!(registry.registrations()[0].state, EntityState::Deleted);
1740 }
1741
1742 let registered = registry.tracked_for::<SnapshotEntity>()[0].clone();
1743
1744 assert_eq!(registry.entry_count(), 1);
1745 assert_eq!(registry.registrations()[0].state, EntityState::Deleted);
1746 assert_eq!(registered.current_clone().name, "changed before delete");
1747 }
1748
1749 #[test]
1750 fn loaded_identity_reattaches_detached_registry_entry_with_owned_snapshots() {
1751 let registry = Arc::new(TrackingRegistry::default());
1752
1753 {
1754 let mut tracked = Tracked::from_loaded(SnapshotEntity {
1755 name: "loaded".to_string(),
1756 });
1757 tracked
1758 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(7))
1759 .unwrap();
1760 tracked.current_mut().name = "changed before drop".to_string();
1761
1762 assert_eq!(tracked.state(), EntityState::Modified);
1763 assert_eq!(registry.entry_count(), 1);
1764 }
1765
1766 let mut reattached = Tracked::from_loaded(SnapshotEntity {
1767 name: "stale database value".to_string(),
1768 });
1769 reattached
1770 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(7))
1771 .unwrap();
1772
1773 assert_eq!(registry.entry_count(), 1);
1774 assert_eq!(reattached.state(), EntityState::Modified);
1775 assert_eq!(reattached.original().name, "loaded");
1776 assert_eq!(reattached.current().name, "changed before drop");
1777 assert_eq!(registry.registrations()[0].state, EntityState::Modified);
1778 assert_eq!(
1779 registry.tracked_for::<SnapshotEntity>()[0]
1780 .current_clone()
1781 .name,
1782 "changed before drop"
1783 );
1784 }
1785
1786 #[test]
1787 fn loaded_identity_reattach_rejects_incompatible_registry_snapshots() {
1788 let registry = Arc::new(TrackingRegistry::default());
1789 let registration_id;
1790
1791 {
1792 let mut tracked = Tracked::from_loaded(SnapshotEntity {
1793 name: "loaded".to_string(),
1794 });
1795 tracked
1796 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(7))
1797 .unwrap();
1798 tracked.current_mut().name = "changed before drop".to_string();
1799 registration_id = tracked.registration_id.expect("registered");
1800 }
1801
1802 registry.set_snapshots(
1803 registration_id,
1804 SnapshotEntityAlias {
1805 name: "wrong original type".to_string(),
1806 },
1807 SnapshotEntityAlias {
1808 name: "wrong current type".to_string(),
1809 },
1810 );
1811
1812 let mut reattached = Tracked::from_loaded(SnapshotEntity {
1813 name: "fresh database value".to_string(),
1814 });
1815 let error = reattached
1816 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(7))
1817 .unwrap_err();
1818
1819 assert_eq!(
1820 error.message(),
1821 "tracked entity `DummyEntity` has incompatible registry snapshots"
1822 );
1823 assert_eq!(error.kind(), OrmErrorKind::Mapping);
1824 assert_eq!(registry.entry_count(), 1);
1825 assert_eq!(reattached.state(), EntityState::Unchanged);
1826 assert_eq!(reattached.current().name, "fresh database value");
1827 }
1828
1829 #[test]
1830 fn detached_loaded_identity_can_be_registered_again() {
1831 let registry = Arc::new(TrackingRegistry::default());
1832 let mut first = Tracked::from_loaded(SnapshotEntity {
1833 name: "first".to_string(),
1834 });
1835 first
1836 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(7))
1837 .unwrap();
1838
1839 first.detach();
1840
1841 let mut second = Tracked::from_loaded(SnapshotEntity {
1842 name: "second".to_string(),
1843 });
1844 second
1845 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(7))
1846 .unwrap();
1847
1848 let registrations = registry.registrations();
1849
1850 assert_eq!(registry.entry_count(), 1);
1851 assert_eq!(registrations[0].entry_id, 1);
1852 assert_eq!(second.current().name, "second");
1853 assert_eq!(second.state(), EntityState::Unchanged);
1854 assert_eq!(
1855 registry
1856 .current_snapshot_for_key::<SnapshotEntity>(SqlValue::I64(7))
1857 .expect("newly registered identity should be available")
1858 .name,
1859 "second"
1860 );
1861 }
1862
1863 #[test]
1864 fn cleared_loaded_identity_can_be_registered_again() {
1865 let registry = Arc::new(TrackingRegistry::default());
1866 let mut first = Tracked::from_loaded(SnapshotEntity {
1867 name: "first".to_string(),
1868 });
1869 first
1870 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(7))
1871 .unwrap();
1872
1873 registry.clear();
1874
1875 let mut second = Tracked::from_loaded(SnapshotEntity {
1876 name: "second".to_string(),
1877 });
1878 second
1879 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(7))
1880 .unwrap();
1881
1882 let registrations = registry.registrations();
1883
1884 assert_eq!(registry.entry_count(), 1);
1885 assert_eq!(registrations[0].entry_id, 1);
1886 assert_eq!(second.current().name, "second");
1887 assert_eq!(second.state(), EntityState::Unchanged);
1888 assert_eq!(
1889 registry
1890 .current_snapshot_for_key::<SnapshotEntity>(SqlValue::I64(7))
1891 .expect("newly registered identity should be available")
1892 .name,
1893 "second"
1894 );
1895 }
1896
1897 #[test]
1898 fn tracked_for_does_not_return_stale_handles_after_clear() {
1899 let registry = Arc::new(TrackingRegistry::default());
1900 let mut first = Tracked::from_loaded(SnapshotEntity {
1901 name: "first".to_string(),
1902 });
1903 let mut second = Tracked::from_loaded(SnapshotEntity {
1904 name: "second".to_string(),
1905 });
1906 first
1907 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(7))
1908 .unwrap();
1909 second
1910 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(8))
1911 .unwrap();
1912
1913 registry.clear();
1914
1915 assert!(registry.tracked_for::<SnapshotEntity>().is_empty());
1916 assert!(registry.registrations().is_empty());
1917 assert_eq!(first.state(), EntityState::Unchanged);
1918 assert_eq!(second.state(), EntityState::Unchanged);
1919 }
1920
1921 #[test]
1922 fn current_snapshot_for_key_syncs_attached_wrapper_current() {
1923 let registry = Arc::new(TrackingRegistry::default());
1924 let mut tracked = Tracked::from_loaded(SnapshotEntity {
1925 name: "loaded".to_string(),
1926 });
1927 tracked
1928 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(7))
1929 .unwrap();
1930
1931 tracked.current_mut().name = "changed through wrapper".to_string();
1932
1933 let snapshot = registry
1934 .current_snapshot_for_key::<SnapshotEntity>(SqlValue::I64(7))
1935 .expect("tracked identity should have a current snapshot");
1936
1937 assert_eq!(snapshot.name, "changed through wrapper");
1938 assert_eq!(tracked.state(), EntityState::Modified);
1939 assert_eq!(registry.registrations()[0].state, EntityState::Modified);
1940 }
1941
1942 #[test]
1943 fn current_snapshot_for_key_scopes_lookup_by_rust_type() {
1944 let registry = Arc::new(TrackingRegistry::default());
1945 let mut tracked = Tracked::from_loaded(SnapshotEntity {
1946 name: "loaded".to_string(),
1947 });
1948 tracked
1949 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(7))
1950 .unwrap();
1951
1952 assert_eq!(
1953 registry.current_snapshot_for_key::<SnapshotEntityAlias>(SqlValue::I64(7)),
1954 None
1955 );
1956 assert_eq!(
1957 registry
1958 .current_snapshot_for_key::<SnapshotEntity>(SqlValue::I64(7))
1959 .expect("tracked identity should have a snapshot")
1960 .name,
1961 "loaded"
1962 );
1963 }
1964
1965 #[test]
1966 fn current_snapshot_for_key_ignores_unregistered_identity() {
1967 let registry = Arc::new(TrackingRegistry::default());
1968 let mut tracked = Tracked::from_loaded(SnapshotEntity {
1969 name: "loaded".to_string(),
1970 });
1971 tracked
1972 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(7))
1973 .unwrap();
1974 let registration_id = tracked.registration_id.expect("registered");
1975
1976 registry.unregister(registration_id);
1977
1978 assert_eq!(
1979 registry.current_snapshot_for_key::<SnapshotEntity>(SqlValue::I64(7)),
1980 None
1981 );
1982 }
1983
1984 #[test]
1985 fn current_snapshot_for_key_ignores_cleared_identity() {
1986 let registry = Arc::new(TrackingRegistry::default());
1987 let mut tracked = Tracked::from_loaded(SnapshotEntity {
1988 name: "loaded".to_string(),
1989 });
1990 tracked
1991 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(7))
1992 .unwrap();
1993
1994 registry.clear();
1995
1996 assert_eq!(
1997 registry.current_snapshot_for_key::<SnapshotEntity>(SqlValue::I64(7)),
1998 None
1999 );
2000 }
2001
2002 #[test]
2003 fn tracking_registry_rejects_duplicate_loaded_identity() {
2004 let registry = Arc::new(TrackingRegistry::default());
2005 let mut first = Tracked::from_loaded(DummyEntity);
2006 let mut second = Tracked::from_loaded(DummyEntity);
2007
2008 first
2009 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(7))
2010 .unwrap();
2011 let error = second
2012 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(7))
2013 .unwrap_err();
2014
2015 assert_eq!(registry.entry_count(), 1);
2016 assert_eq!(
2017 error.message(),
2018 "entity `DummyEntity` with primary key value `I64(7)` already has a live tracked handle in this context; detach or drop the existing handle before loading it again"
2019 );
2020 assert_eq!(error.kind(), OrmErrorKind::Compile);
2021 }
2022
2023 #[test]
2024 fn duplicate_loaded_identity_error_leaves_rejected_wrapper_detached() {
2025 let registry = Arc::new(TrackingRegistry::default());
2026 let mut first = Tracked::from_loaded(DummyEntity);
2027
2028 first
2029 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(7))
2030 .unwrap();
2031
2032 {
2033 let mut duplicate = Tracked::from_loaded(DummyEntity);
2034 let error = duplicate
2035 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(7))
2036 .unwrap_err();
2037
2038 assert_eq!(
2039 error.message(),
2040 "entity `DummyEntity` with primary key value `I64(7)` already has a live tracked handle in this context; detach or drop the existing handle before loading it again"
2041 );
2042 assert_eq!(error.kind(), OrmErrorKind::Compile);
2043 assert_eq!(duplicate.state(), EntityState::Unchanged);
2044 assert_eq!(registry.entry_count(), 1);
2045 }
2046
2047 assert_eq!(registry.entry_count(), 1);
2048 assert_eq!(registry.registrations()[0].state, EntityState::Unchanged);
2049 }
2050
2051 #[test]
2052 fn tracking_registry_scopes_loaded_identity_by_rust_type() {
2053 let registry = Arc::new(TrackingRegistry::default());
2054 let mut first = Tracked::from_loaded(DummyEntity);
2055 let mut second = Tracked::from_loaded(DummyEntityAlias);
2056
2057 first
2058 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(7))
2059 .unwrap();
2060 second
2061 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(7))
2062 .unwrap();
2063
2064 assert_eq!(registry.entry_count(), 2);
2065 }
2066
2067 #[test]
2068 fn tracking_registry_allows_multiple_added_entities_with_temporary_identities() {
2069 let registry = Arc::new(TrackingRegistry::default());
2070 let mut first = Tracked::from_added(DummyEntity);
2071 let mut second = Tracked::from_added(DummyEntity);
2072
2073 first.attach_registry_added(Arc::clone(®istry));
2074 second.attach_registry_added(Arc::clone(®istry));
2075
2076 assert_eq!(registry.entry_count(), 2);
2077 }
2078
2079 #[test]
2080 fn tracking_registry_updates_temporary_identity_to_persisted_identity() {
2081 let registry = Arc::new(TrackingRegistry::default());
2082 let mut tracked = Tracked::from_added(DummyEntity);
2083 tracked.attach_registry_added(Arc::clone(®istry));
2084
2085 registry
2086 .update_persisted_identity::<DummyEntity>(
2087 tracked.registration_id.expect("registered"),
2088 SqlValue::I64(11),
2089 )
2090 .unwrap();
2091
2092 let mut duplicate = Tracked::from_loaded(DummyEntity);
2093 let error = duplicate
2094 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(11))
2095 .unwrap_err();
2096
2097 assert!(error.message().contains("live tracked handle"));
2098 assert_eq!(error.kind(), OrmErrorKind::Compile);
2099 }
2100
2101 #[test]
2102 fn tracking_registry_rejects_persisted_identity_update_collision_without_mutating_entry() {
2103 let registry = Arc::new(TrackingRegistry::default());
2104 let mut existing = Tracked::from_loaded(DummyEntity);
2105 let mut pending = Tracked::from_added(DummyEntity);
2106
2107 existing
2108 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(11))
2109 .unwrap();
2110 pending.attach_registry_added(Arc::clone(®istry));
2111
2112 let pending_registration = pending.registration_id.expect("registered pending entity");
2113 let error = registry
2114 .update_persisted_identity::<DummyEntity>(pending_registration, SqlValue::I64(11))
2115 .unwrap_err();
2116
2117 assert!(error.message().contains("already tracked"));
2118 assert_eq!(error.kind(), OrmErrorKind::Compile);
2119 assert_eq!(registry.entry_count(), 2);
2120
2121 let mut duplicate = Tracked::from_loaded(DummyEntity);
2122 let duplicate_error = duplicate
2123 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(11))
2124 .unwrap_err();
2125 assert!(duplicate_error.message().contains("live tracked handle"));
2126
2127 registry
2128 .update_persisted_identity::<DummyEntity>(pending_registration, SqlValue::I64(12))
2129 .unwrap();
2130
2131 let mut second_duplicate = Tracked::from_loaded(DummyEntity);
2132 let second_duplicate_error = second_duplicate
2133 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(12))
2134 .unwrap_err();
2135 assert!(
2136 second_duplicate_error
2137 .message()
2138 .contains("live tracked handle")
2139 );
2140 }
2141
2142 #[test]
2143 fn tracking_registry_rejects_persisted_identity_update_collision_with_detached_entry() {
2144 let registry = Arc::new(TrackingRegistry::default());
2145
2146 {
2147 let mut existing = Tracked::from_loaded(SnapshotEntity {
2148 name: "existing".to_string(),
2149 });
2150 existing
2151 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(11))
2152 .unwrap();
2153 existing.current_mut().name = "existing changed".to_string();
2154 }
2155
2156 let mut pending = Tracked::from_added(SnapshotEntity {
2157 name: "pending".to_string(),
2158 });
2159 pending.attach_registry_added(Arc::clone(®istry));
2160 let pending_registration = pending.registration_id.expect("registered pending entity");
2161
2162 let error = registry
2163 .update_persisted_identity::<SnapshotEntity>(pending_registration, SqlValue::I64(11))
2164 .unwrap_err();
2165
2166 assert_eq!(
2167 error.message(),
2168 "entity `DummyEntity` with primary key value `I64(11)` is already tracked in this context"
2169 );
2170 assert_eq!(error.kind(), OrmErrorKind::Compile);
2171 assert_eq!(registry.entry_count(), 2);
2172
2173 let mut reattached_existing = Tracked::from_loaded(SnapshotEntity {
2174 name: "fresh database value".to_string(),
2175 });
2176 reattached_existing
2177 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(11))
2178 .unwrap();
2179
2180 assert_eq!(reattached_existing.current().name, "existing changed");
2181
2182 registry
2183 .update_persisted_identity::<SnapshotEntity>(pending_registration, SqlValue::I64(12))
2184 .unwrap();
2185
2186 let mut duplicate_pending = Tracked::from_loaded(SnapshotEntity {
2187 name: "duplicate pending".to_string(),
2188 });
2189 let duplicate_error = duplicate_pending
2190 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(12))
2191 .unwrap_err();
2192
2193 assert!(duplicate_error.message().contains("live tracked handle"));
2194 }
2195
2196 #[test]
2197 fn tracking_registry_rejects_persisted_identity_update_for_missing_registration() {
2198 let registry = TrackingRegistry::default();
2199
2200 let error = registry
2201 .update_persisted_identity::<DummyEntity>(99, SqlValue::I64(11))
2202 .unwrap_err();
2203
2204 assert_eq!(error.message(), "tracked entity registration was not found");
2205 assert_eq!(error.kind(), OrmErrorKind::Execution);
2206 }
2207
2208 #[test]
2209 fn tracking_registry_missing_registration_error_precedes_identity_collision() {
2210 let registry = Arc::new(TrackingRegistry::default());
2211 let mut existing = Tracked::from_loaded(DummyEntity);
2212 existing
2213 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(11))
2214 .unwrap();
2215
2216 let error = registry
2217 .update_persisted_identity::<DummyEntity>(99, SqlValue::I64(11))
2218 .unwrap_err();
2219
2220 assert_eq!(error.message(), "tracked entity registration was not found");
2221 assert_eq!(error.kind(), OrmErrorKind::Execution);
2222 assert_eq!(registry.entry_count(), 1);
2223 assert_eq!(registry.registrations()[0].entry_id, 0);
2224 }
2225
2226 #[test]
2227 fn tracking_registry_clear_removes_all_entries() {
2228 let registry = Arc::new(TrackingRegistry::default());
2229 let mut first = Tracked::from_added(DummyEntity);
2230 let mut second = Tracked::from_added(DummyEntity);
2231 first.attach_registry_added(Arc::clone(®istry));
2232 second.attach_registry_added(Arc::clone(®istry));
2233
2234 registry.clear();
2235
2236 assert_eq!(registry.entry_count(), 0);
2237 assert!(registry.registrations().is_empty());
2238 }
2239
2240 #[test]
2241 fn detach_registry_unregisters_without_dropping_wrapper() {
2242 let registry = Arc::new(TrackingRegistry::default());
2243 let mut tracked = Tracked::from_loaded(DummyEntity);
2244 tracked.attach_registry(Arc::clone(®istry));
2245
2246 tracked.detach_registry();
2247
2248 assert_eq!(registry.entry_count(), 0);
2249 assert_eq!(tracked.state(), EntityState::Unchanged);
2250 }
2251
2252 #[test]
2253 fn public_detach_is_idempotent_and_keeps_visible_state() {
2254 let registry = Arc::new(TrackingRegistry::default());
2255 let mut tracked = Tracked::from_loaded(DummyEntity);
2256 tracked.attach_registry(Arc::clone(®istry));
2257 tracked.mark_deleted();
2258
2259 tracked.detach();
2260 tracked.detach();
2261
2262 assert_eq!(registry.entry_count(), 0);
2263 assert_eq!(tracked.state(), EntityState::Deleted);
2264 }
2265
2266 #[test]
2267 fn public_detach_unregisters_without_resetting_state() {
2268 let registry = Arc::new(TrackingRegistry::default());
2269 let mut tracked = Tracked::from_loaded(DummyEntity);
2270 tracked.attach_registry(Arc::clone(®istry));
2271 tracked.mark_modified();
2272
2273 tracked.detach();
2274
2275 assert_eq!(registry.entry_count(), 0);
2276 assert_eq!(tracked.state(), EntityState::Modified);
2277 }
2278
2279 #[test]
2280 fn tracking_registry_unregister_missing_registration_is_noop() {
2281 let registry = Arc::new(TrackingRegistry::default());
2282 let mut tracked = Tracked::from_loaded(DummyEntity);
2283 tracked.attach_registry(Arc::clone(®istry));
2284
2285 registry.unregister(99);
2286
2287 assert_eq!(registry.entry_count(), 1);
2288 assert_eq!(registry.registrations()[0].state, EntityState::Unchanged);
2289 }
2290
2291 #[test]
2292 fn dropping_tracked_entity_unregisters_it_from_registry() {
2293 let registry = Arc::new(TrackingRegistry::default());
2294
2295 {
2296 let mut tracked = Tracked::from_loaded(DummyEntity);
2297 tracked.attach_registry(Arc::clone(®istry));
2298 assert_eq!(registry.entry_count(), 1);
2299 }
2300
2301 assert_eq!(registry.entry_count(), 0);
2302 }
2303
2304 #[test]
2305 fn save_changes_plan_orders_added_parents_before_children() {
2306 let plan = save_changes_operation_plan(&[
2307 &ORDER_ITEM_METADATA,
2308 &DUMMY_ENTITY_METADATA,
2309 &ORDER_METADATA,
2310 ])
2311 .unwrap();
2312
2313 assert_eq!(plan.added_order(), &[1, 2, 0]);
2314 assert_eq!(plan.modified_order(), &[1, 2, 0]);
2315 }
2316
2317 #[test]
2318 fn save_changes_plan_orders_deleted_children_before_parents() {
2319 let plan = save_changes_operation_plan(&[
2320 &ORDER_ITEM_METADATA,
2321 &DUMMY_ENTITY_METADATA,
2322 &ORDER_METADATA,
2323 ])
2324 .unwrap();
2325
2326 assert_eq!(plan.deleted_order(), &[0, 2, 1]);
2327 }
2328
2329 #[test]
2330 fn save_changes_plan_preserves_context_order_without_dependencies() {
2331 let plan = save_changes_operation_plan(&[&ORDER_METADATA, &DUMMY_ENTITY_METADATA]).unwrap();
2332
2333 assert_eq!(plan.added_order(), &[0, 1]);
2334 assert_eq!(plan.modified_order(), &[0, 1]);
2335 assert_eq!(plan.deleted_order(), &[1, 0]);
2336 }
2337
2338 #[test]
2339 fn save_changes_plan_ignores_foreign_keys_to_entities_outside_context() {
2340 let plan =
2341 save_changes_operation_plan(&[&ORDER_ITEM_METADATA, &DUMMY_ENTITY_METADATA]).unwrap();
2342
2343 assert_eq!(plan.added_order(), &[0, 1]);
2344 assert_eq!(plan.modified_order(), &[0, 1]);
2345 assert_eq!(plan.deleted_order(), &[1, 0]);
2346 }
2347
2348 #[test]
2349 fn save_changes_plan_ignores_simple_self_references() {
2350 let plan =
2351 save_changes_operation_plan(&[&CATEGORY_METADATA, &DUMMY_ENTITY_METADATA]).unwrap();
2352
2353 assert_eq!(plan.added_order(), &[0, 1]);
2354 assert_eq!(plan.modified_order(), &[0, 1]);
2355 assert_eq!(plan.deleted_order(), &[1, 0]);
2356 }
2357
2358 #[test]
2359 fn save_changes_plan_rejects_foreign_key_cycles() {
2360 let error =
2361 save_changes_operation_plan(&[&CYCLE_A_METADATA, &CYCLE_B_METADATA]).unwrap_err();
2362
2363 assert!(error.message().contains("foreign-key cycle"));
2364 assert_eq!(error.kind(), OrmErrorKind::Compile);
2365 }
2366}