1use crate::EntityPersist;
62use core::ops::{Deref, DerefMut};
63use sql_orm_core::{Entity, EntityMetadata, OrmError, SqlValue};
64use std::any::TypeId;
65use std::marker::PhantomData;
66use std::sync::{Arc, Mutex};
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub enum EntityState {
71 Unchanged,
73 Added,
75 Modified,
77 Deleted,
79}
80
81pub struct Tracked<T> {
98 inner: Box<TrackedInner<T>>,
99 registration_id: Option<usize>,
100 tracking_registry: Option<TrackingRegistryHandle>,
101}
102
103#[doc(hidden)]
104#[derive(Debug, Clone, PartialEq, Eq)]
105pub struct TrackedEntityRegistration {
106 pub entity_rust_name: &'static str,
107 pub state: EntityState,
108}
109
110#[doc(hidden)]
111#[derive(Debug, Default)]
112pub struct TrackingRegistry {
113 state: Mutex<TrackingRegistryState>,
114}
115
116#[doc(hidden)]
117pub type TrackingRegistryHandle = Arc<TrackingRegistry>;
118
119#[doc(hidden)]
120#[derive(Debug, Clone, PartialEq, Eq)]
121pub struct SaveChangesOperationPlan {
122 added_order: Vec<usize>,
123 modified_order: Vec<usize>,
124 deleted_order: Vec<usize>,
125}
126
127struct TrackedInner<T> {
128 original: T,
129 current: T,
130 state: EntityState,
131}
132
133#[derive(Debug, Default)]
134struct TrackingRegistryState {
135 next_registration_id: usize,
136 next_temporary_identity: u64,
137 entries: Vec<TrackingRegistration>,
138}
139
140#[derive(Debug)]
141struct TrackingRegistration {
142 registration_id: usize,
143 identity: TrackedIdentity,
144 entity_type_id: TypeId,
145 entity_rust_name: &'static str,
146 inner_address: usize,
147 state_reader: unsafe fn(*const ()) -> EntityState,
148}
149
150#[derive(Debug, Clone, PartialEq)]
151struct TrackedIdentity {
152 entity_type_id: TypeId,
153 entity_rust_name: &'static str,
154 schema: &'static str,
155 table: &'static str,
156 primary_key: TrackedPrimaryKeyIdentity,
157}
158
159#[derive(Debug, Clone, PartialEq)]
160enum TrackedPrimaryKeyIdentity {
161 Simple(SqlValue),
162 Temporary(u64),
163}
164
165#[derive(Clone, Copy)]
166pub(crate) struct RegisteredTracked<E> {
167 registration_id: usize,
168 inner_address: usize,
169 _entity: PhantomData<fn() -> E>,
170}
171
172impl<T: Clone> Tracked<T> {
173 pub fn from_loaded(entity: T) -> Self {
175 Self {
176 inner: Box::new(TrackedInner {
177 original: entity.clone(),
178 current: entity,
179 state: EntityState::Unchanged,
180 }),
181 registration_id: None,
182 tracking_registry: None,
183 }
184 }
185
186 pub fn from_added(entity: T) -> Self {
188 Self {
189 inner: Box::new(TrackedInner {
190 original: entity.clone(),
191 current: entity,
192 state: EntityState::Added,
193 }),
194 registration_id: None,
195 tracking_registry: None,
196 }
197 }
198}
199
200impl<T> Tracked<T> {
201 pub fn original(&self) -> &T {
203 &self.inner.original
204 }
205
206 pub fn current(&self) -> &T {
208 &self.inner.current
209 }
210
211 pub const fn state(&self) -> EntityState {
213 self.inner.state
214 }
215
216 pub fn mark_modified(&mut self) {
222 self.mark_modified_if_unchanged();
223 }
224
225 pub fn mark_deleted(&mut self) {
231 let was_added = self.inner.state == EntityState::Added;
232 self.inner.state = EntityState::Deleted;
233 if was_added {
234 self.detach_registry();
235 }
236 }
237
238 pub fn mark_unchanged(&mut self)
244 where
245 T: Clone,
246 {
247 self.inner.original = self.inner.current.clone();
248 self.inner.state = EntityState::Unchanged;
249 }
250
251 pub fn detach(&mut self) {
256 self.detach_registry();
257 }
258
259 pub fn current_mut(&mut self) -> &mut T {
262 self.mark_modified_if_unchanged();
263 &mut self.inner.current
264 }
265
266 pub(crate) fn current_mut_without_state_change(&mut self) -> &mut T {
267 &mut self.inner.current
268 }
269
270 fn mark_modified_if_unchanged(&mut self) {
271 if self.inner.state == EntityState::Unchanged {
272 self.inner.state = EntityState::Modified;
273 }
274 }
275
276 pub(crate) fn detach_registry(&mut self) {
277 if let (Some(registration_id), Some(registry)) =
278 (self.registration_id.take(), self.tracking_registry.take())
279 {
280 registry.unregister(registration_id);
281 }
282 }
283
284 pub fn save<C>(
293 &mut self,
294 db: &C,
295 ) -> impl core::future::Future<Output = Result<(), OrmError>> + Send
296 where
297 C: crate::DbContextEntitySet<T> + Sync,
298 T: crate::ActiveRecord
299 + crate::AuditEntity
300 + crate::EntityPersist
301 + crate::EntityPrimaryKey
302 + crate::SoftDeleteEntity
303 + crate::TenantScopedEntity
304 + Clone
305 + sql_orm_core::FromRow
306 + Send,
307 {
308 async move {
309 match self.inner.state {
310 EntityState::Unchanged => Ok(()),
311 EntityState::Deleted => Err(OrmError::new(
312 "tracked deleted entities cannot be saved; detach them or persist deletion",
313 )),
314 EntityState::Added | EntityState::Modified => {
315 crate::ActiveRecord::save(&mut self.inner.current, db).await?;
316 self.inner.original = self.inner.current.clone();
317 self.inner.state = EntityState::Unchanged;
318
319 if let (Some(registration_id), Some(registry)) =
320 (self.registration_id, self.tracking_registry.as_ref())
321 {
322 let key =
323 <T as crate::EntityPrimaryKey>::primary_key_value(&self.inner.current)?;
324 registry.update_persisted_identity::<T>(registration_id, key)?;
325 }
326
327 Ok(())
328 }
329 }
330 }
331 }
332
333 pub fn delete<C>(
341 &mut self,
342 db: &C,
343 ) -> impl core::future::Future<Output = Result<bool, OrmError>> + Send
344 where
345 C: crate::DbContextEntitySet<T> + Sync,
346 T: crate::ActiveRecord
347 + crate::EntityPersist
348 + crate::EntityPrimaryKey
349 + crate::SoftDeleteEntity
350 + crate::TenantScopedEntity
351 + Clone
352 + sql_orm_core::FromRow
353 + Send,
354 {
355 async move {
356 match self.inner.state {
357 EntityState::Added => {
358 self.inner.state = EntityState::Deleted;
359 self.detach_registry();
360 Ok(false)
361 }
362 EntityState::Deleted => Ok(false),
363 EntityState::Unchanged | EntityState::Modified => {
364 let deleted = crate::ActiveRecord::delete(&self.inner.current, db).await?;
365 if deleted {
366 self.inner.state = EntityState::Deleted;
367 self.detach_registry();
368 }
369 Ok(deleted)
370 }
371 }
372 }
373 }
374}
375
376impl<T: Clone> Tracked<T> {
377 pub fn into_current(self) -> T {
379 self.current().clone()
380 }
381}
382
383impl<T: Entity> Tracked<T> {
384 pub(crate) fn attach_registry_loaded(
385 &mut self,
386 registry: TrackingRegistryHandle,
387 key: SqlValue,
388 ) -> Result<(), OrmError> {
389 let registration_id = registry.register_loaded(self, key)?;
390 self.registration_id = Some(registration_id);
391 self.tracking_registry = Some(registry);
392 Ok(())
393 }
394
395 pub(crate) fn attach_registry_added(&mut self, registry: TrackingRegistryHandle) {
396 let registration_id = registry.register_added(self);
397 self.registration_id = Some(registration_id);
398 self.tracking_registry = Some(registry);
399 }
400
401 #[cfg(test)]
402 pub(crate) fn attach_registry(&mut self, registry: TrackingRegistryHandle) {
403 self.attach_registry_added(registry);
404 }
405}
406
407impl<T> Deref for Tracked<T> {
408 type Target = T;
409
410 fn deref(&self) -> &Self::Target {
411 self.current()
412 }
413}
414
415impl<T> DerefMut for Tracked<T> {
416 fn deref_mut(&mut self) -> &mut Self::Target {
417 self.current_mut()
418 }
419}
420
421impl TrackingRegistry {
422 pub(crate) fn register_loaded<E: Entity>(
423 &self,
424 tracked: &Tracked<E>,
425 key: SqlValue,
426 ) -> Result<usize, OrmError> {
427 let identity =
428 TrackedIdentity::for_entity::<E>(TrackedPrimaryKeyIdentity::Simple(key.clone()));
429 let mut state = self.state.lock().expect("tracking registry mutex poisoned");
430 if state.entries.iter().any(|entry| entry.identity == identity) {
431 return Err(OrmError::new(format!(
432 "entity `{}` with primary key value `{:?}` is already tracked in this context",
433 E::metadata().rust_name,
434 key
435 )));
436 }
437
438 Ok(state.push_registration(tracked, identity))
439 }
440
441 pub(crate) fn register_added<E: Entity>(&self, tracked: &Tracked<E>) -> usize {
442 let mut state = self.state.lock().expect("tracking registry mutex poisoned");
443 let temporary_identity = state.next_temporary_identity;
444 state.next_temporary_identity += 1;
445 let identity = TrackedIdentity::for_entity::<E>(TrackedPrimaryKeyIdentity::Temporary(
446 temporary_identity,
447 ));
448 state.push_registration(tracked, identity)
449 }
450
451 pub(crate) fn unregister(&self, registration_id: usize) {
452 let mut state = self.state.lock().expect("tracking registry mutex poisoned");
453 state
454 .entries
455 .retain(|entry| entry.registration_id != registration_id);
456 }
457
458 pub fn clear(&self) {
459 self.state
460 .lock()
461 .expect("tracking registry mutex poisoned")
462 .entries
463 .clear();
464 }
465
466 pub(crate) fn tracked_for<E: Entity>(&self) -> Vec<RegisteredTracked<E>> {
467 let state = self.state.lock().expect("tracking registry mutex poisoned");
468
469 state
470 .entries
471 .iter()
472 .filter(|entry| entry.entity_type_id == TypeId::of::<E>())
473 .map(|entry| RegisteredTracked::<E> {
474 registration_id: entry.registration_id,
475 inner_address: entry.inner_address,
476 _entity: PhantomData,
477 })
478 .collect()
479 }
480
481 pub(crate) fn update_persisted_identity<E: Entity>(
482 &self,
483 registration_id: usize,
484 key: SqlValue,
485 ) -> Result<(), OrmError> {
486 let identity =
487 TrackedIdentity::for_entity::<E>(TrackedPrimaryKeyIdentity::Simple(key.clone()));
488 let mut state = self.state.lock().expect("tracking registry mutex poisoned");
489
490 if state
491 .entries
492 .iter()
493 .any(|entry| entry.registration_id != registration_id && entry.identity == identity)
494 {
495 return Err(OrmError::new(format!(
496 "entity `{}` with primary key value `{:?}` is already tracked in this context",
497 E::metadata().rust_name,
498 key
499 )));
500 }
501
502 let entry = state
503 .entries
504 .iter_mut()
505 .find(|entry| entry.registration_id == registration_id)
506 .ok_or_else(|| OrmError::new("tracked entity registration was not found"))?;
507 entry.identity = identity;
508 Ok(())
509 }
510
511 pub fn entry_count(&self) -> usize {
512 self.state
513 .lock()
514 .expect("tracking registry mutex poisoned")
515 .entries
516 .len()
517 }
518
519 pub fn registrations(&self) -> Vec<TrackedEntityRegistration> {
520 self.state
521 .lock()
522 .expect("tracking registry mutex poisoned")
523 .entries
524 .iter()
525 .map(|entry| TrackedEntityRegistration {
526 entity_rust_name: entry.entity_rust_name,
527 state: unsafe { (entry.state_reader)(entry.inner_address as *const ()) },
528 })
529 .collect()
530 }
531}
532
533#[doc(hidden)]
534pub fn save_changes_operation_plan(
535 entities: &[&'static EntityMetadata],
536) -> Result<SaveChangesOperationPlan, OrmError> {
537 let insert_order = topological_entity_order(entities)?;
538 let mut delete_order = insert_order.clone();
539 delete_order.reverse();
540
541 Ok(SaveChangesOperationPlan {
542 added_order: insert_order.clone(),
543 modified_order: insert_order,
544 deleted_order: delete_order,
545 })
546}
547
548impl SaveChangesOperationPlan {
549 pub fn added_order(&self) -> &[usize] {
550 &self.added_order
551 }
552
553 pub fn modified_order(&self) -> &[usize] {
554 &self.modified_order
555 }
556
557 pub fn deleted_order(&self) -> &[usize] {
558 &self.deleted_order
559 }
560}
561
562fn topological_entity_order(entities: &[&'static EntityMetadata]) -> Result<Vec<usize>, OrmError> {
563 let mut outgoing_edges = vec![Vec::<usize>::new(); entities.len()];
564 let mut incoming_edge_count = vec![0usize; entities.len()];
565
566 for (child_index, child) in entities.iter().enumerate() {
567 for foreign_key in child.foreign_keys {
568 if foreign_key.columns.len() != 1 || foreign_key.referenced_columns.len() != 1 {
569 continue;
570 }
571
572 let Some(parent_index) = entities.iter().position(|candidate| {
573 candidate.schema == foreign_key.referenced_schema
574 && candidate.table == foreign_key.referenced_table
575 }) else {
576 continue;
577 };
578
579 if parent_index == child_index || outgoing_edges[parent_index].contains(&child_index) {
580 continue;
581 }
582
583 outgoing_edges[parent_index].push(child_index);
584 incoming_edge_count[child_index] += 1;
585 }
586 }
587
588 let mut order = Vec::with_capacity(entities.len());
589 let mut ready: Vec<usize> = incoming_edge_count
590 .iter()
591 .enumerate()
592 .filter_map(|(index, count)| (*count == 0).then_some(index))
593 .collect();
594
595 while !ready.is_empty() {
596 ready.sort_unstable();
597 let entity_index = ready.remove(0);
598 order.push(entity_index);
599
600 for child_index in &outgoing_edges[entity_index] {
601 incoming_edge_count[*child_index] -= 1;
602 if incoming_edge_count[*child_index] == 0 {
603 ready.push(*child_index);
604 }
605 }
606 }
607
608 if order.len() != entities.len() {
609 return Err(OrmError::new(
610 "save_changes cannot determine a deterministic order for tracked operations because the context contains a foreign-key cycle",
611 ));
612 }
613
614 Ok(order)
615}
616
617impl TrackingRegistryState {
618 fn push_registration<E: Entity>(
619 &mut self,
620 tracked: &Tracked<E>,
621 identity: TrackedIdentity,
622 ) -> usize {
623 let registration_id = self.next_registration_id;
624 self.next_registration_id += 1;
625 self.entries.push(TrackingRegistration {
626 registration_id,
627 identity,
628 entity_type_id: TypeId::of::<E>(),
629 entity_rust_name: E::metadata().rust_name,
630 inner_address: tracked.inner.as_ref() as *const TrackedInner<E> as usize,
631 state_reader: state_reader::<E>,
632 });
633 registration_id
634 }
635}
636
637impl TrackedIdentity {
638 fn for_entity<E: Entity>(primary_key: TrackedPrimaryKeyIdentity) -> Self {
639 let metadata = E::metadata();
640 Self {
641 entity_type_id: TypeId::of::<E>(),
642 entity_rust_name: metadata.rust_name,
643 schema: metadata.schema,
644 table: metadata.table,
645 primary_key,
646 }
647 }
648}
649
650impl<E: Clone> RegisteredTracked<E> {
651 pub(crate) fn registration_id(&self) -> usize {
652 self.registration_id
653 }
654
655 pub(crate) fn state(&self) -> EntityState {
656 unsafe { (&*(self.inner_address as *const TrackedInner<E>)).state }
657 }
658
659 pub(crate) fn current_clone(&self) -> E {
660 unsafe {
661 (&*(self.inner_address as *const TrackedInner<E>))
662 .current
663 .clone()
664 }
665 }
666
667 pub(crate) fn accept_current(&self) {
668 unsafe {
669 let inner = self.inner_address as *mut TrackedInner<E>;
670 (*inner).original = (*inner).current.clone();
671 (*inner).state = EntityState::Unchanged;
672 }
673 }
674
675 pub(crate) fn sync_persisted(&self, persisted: E) {
676 unsafe {
677 let inner = self.inner_address as *mut TrackedInner<E>;
678 (*inner).original = persisted.clone();
679 (*inner).current = persisted;
680 (*inner).state = EntityState::Unchanged;
681 }
682 }
683}
684
685impl<E: EntityPersist> RegisteredTracked<E> {
686 pub(crate) fn has_persisted_changes(&self) -> bool {
687 unsafe {
688 let inner = &*(self.inner_address as *const TrackedInner<E>);
689 E::has_persisted_changes(&inner.original, &inner.current)
690 }
691 }
692}
693
694unsafe fn state_reader<E>(ptr: *const ()) -> EntityState {
695 unsafe { (&*(ptr.cast::<TrackedInner<E>>())).state }
696}
697
698impl<T: Clone> Clone for Tracked<T> {
699 fn clone(&self) -> Self {
700 Self {
701 inner: Box::new(TrackedInner {
702 original: self.original().clone(),
703 current: self.current().clone(),
704 state: self.state(),
705 }),
706 registration_id: None,
707 tracking_registry: None,
708 }
709 }
710}
711
712impl<T: core::fmt::Debug> core::fmt::Debug for Tracked<T> {
713 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
714 f.debug_struct("Tracked")
715 .field("original", self.original())
716 .field("current", self.current())
717 .field("state", &self.state())
718 .finish()
719 }
720}
721
722impl<T: PartialEq> PartialEq for Tracked<T> {
723 fn eq(&self, other: &Self) -> bool {
724 self.original() == other.original()
725 && self.current() == other.current()
726 && self.state() == other.state()
727 }
728}
729
730impl<T: Eq> Eq for Tracked<T> {}
731
732impl<T> Drop for Tracked<T> {
733 fn drop(&mut self) {
734 if let (Some(registration_id), Some(registry)) =
735 (self.registration_id.take(), self.tracking_registry.take())
736 {
737 registry.unregister(registration_id);
738 }
739 }
740}
741
742#[cfg(test)]
743mod tests {
744 use super::{
745 EntityState, Tracked, TrackedEntityRegistration, TrackingRegistry,
746 save_changes_operation_plan,
747 };
748 use sql_orm_core::{
749 Entity, EntityMetadata, ForeignKeyMetadata, PrimaryKeyMetadata, ReferentialAction, SqlValue,
750 };
751 use std::sync::Arc;
752
753 #[derive(Clone)]
754 struct DummyEntity;
755
756 #[derive(Clone)]
757 struct DummyEntityAlias;
758
759 static DUMMY_ENTITY_METADATA: EntityMetadata = EntityMetadata {
760 rust_name: "DummyEntity",
761 schema: "dbo",
762 table: "dummy_entities",
763 renamed_from: None,
764 columns: &[],
765 primary_key: PrimaryKeyMetadata {
766 name: None,
767 columns: &[],
768 },
769 indexes: &[],
770 foreign_keys: &[],
771 navigations: &[],
772 };
773
774 static ORDER_METADATA: EntityMetadata = EntityMetadata {
775 rust_name: "Order",
776 schema: "sales",
777 table: "orders",
778 renamed_from: None,
779 columns: &[],
780 primary_key: PrimaryKeyMetadata {
781 name: None,
782 columns: &["id"],
783 },
784 indexes: &[],
785 foreign_keys: &[],
786 navigations: &[],
787 };
788
789 static ORDER_ITEM_FOREIGN_KEYS: [ForeignKeyMetadata; 1] = [ForeignKeyMetadata::new(
790 "fk_order_items_orders",
791 &["order_id"],
792 "sales",
793 "orders",
794 &["id"],
795 ReferentialAction::NoAction,
796 ReferentialAction::NoAction,
797 )];
798
799 static ORDER_ITEM_METADATA: EntityMetadata = EntityMetadata {
800 rust_name: "OrderItem",
801 schema: "sales",
802 table: "order_items",
803 renamed_from: None,
804 columns: &[],
805 primary_key: PrimaryKeyMetadata {
806 name: None,
807 columns: &["id"],
808 },
809 indexes: &[],
810 foreign_keys: &ORDER_ITEM_FOREIGN_KEYS,
811 navigations: &[],
812 };
813
814 static CATEGORY_FOREIGN_KEYS: [ForeignKeyMetadata; 1] = [ForeignKeyMetadata::new(
815 "fk_categories_parent",
816 &["parent_id"],
817 "catalog",
818 "categories",
819 &["id"],
820 ReferentialAction::NoAction,
821 ReferentialAction::NoAction,
822 )];
823
824 static CATEGORY_METADATA: EntityMetadata = EntityMetadata {
825 rust_name: "Category",
826 schema: "catalog",
827 table: "categories",
828 renamed_from: None,
829 columns: &[],
830 primary_key: PrimaryKeyMetadata {
831 name: None,
832 columns: &["id"],
833 },
834 indexes: &[],
835 foreign_keys: &CATEGORY_FOREIGN_KEYS,
836 navigations: &[],
837 };
838
839 static CYCLE_A_FOREIGN_KEYS: [ForeignKeyMetadata; 1] = [ForeignKeyMetadata::new(
840 "fk_cycle_a_cycle_b",
841 &["cycle_b_id"],
842 "dbo",
843 "cycle_b",
844 &["id"],
845 ReferentialAction::NoAction,
846 ReferentialAction::NoAction,
847 )];
848
849 static CYCLE_B_FOREIGN_KEYS: [ForeignKeyMetadata; 1] = [ForeignKeyMetadata::new(
850 "fk_cycle_b_cycle_a",
851 &["cycle_a_id"],
852 "dbo",
853 "cycle_a",
854 &["id"],
855 ReferentialAction::NoAction,
856 ReferentialAction::NoAction,
857 )];
858
859 static CYCLE_A_METADATA: EntityMetadata = EntityMetadata {
860 rust_name: "CycleA",
861 schema: "dbo",
862 table: "cycle_a",
863 renamed_from: None,
864 columns: &[],
865 primary_key: PrimaryKeyMetadata {
866 name: None,
867 columns: &["id"],
868 },
869 indexes: &[],
870 foreign_keys: &CYCLE_A_FOREIGN_KEYS,
871 navigations: &[],
872 };
873
874 static CYCLE_B_METADATA: EntityMetadata = EntityMetadata {
875 rust_name: "CycleB",
876 schema: "dbo",
877 table: "cycle_b",
878 renamed_from: None,
879 columns: &[],
880 primary_key: PrimaryKeyMetadata {
881 name: None,
882 columns: &["id"],
883 },
884 indexes: &[],
885 foreign_keys: &CYCLE_B_FOREIGN_KEYS,
886 navigations: &[],
887 };
888
889 impl Entity for DummyEntity {
890 fn metadata() -> &'static EntityMetadata {
891 &DUMMY_ENTITY_METADATA
892 }
893 }
894
895 impl Entity for DummyEntityAlias {
896 fn metadata() -> &'static EntityMetadata {
897 &DUMMY_ENTITY_METADATA
898 }
899 }
900
901 #[test]
902 fn tracked_loaded_value_keeps_original_and_current_snapshots() {
903 let tracked = Tracked::from_loaded(String::from("Ana"));
904
905 assert_eq!(tracked.state(), EntityState::Unchanged);
906 assert_eq!(tracked.original(), "Ana");
907 assert_eq!(tracked.current(), "Ana");
908 }
909
910 #[test]
911 fn tracked_added_value_starts_in_added_state() {
912 let tracked = Tracked::from_added(String::from("Luis"));
913
914 assert_eq!(tracked.state(), EntityState::Added);
915 assert_eq!(tracked.original(), "Luis");
916 assert_eq!(tracked.current(), "Luis");
917 }
918
919 #[test]
920 fn tracked_can_release_current_value() {
921 let tracked = Tracked::from_loaded(String::from("Maria"));
922
923 assert_eq!(tracked.into_current(), "Maria");
924 }
925
926 #[test]
927 fn into_current_consumes_registered_wrapper_and_unregisters_it() {
928 let registry = Arc::new(TrackingRegistry::default());
929 let mut tracked = Tracked::from_loaded(DummyEntity);
930 tracked.attach_registry(Arc::clone(®istry));
931
932 assert_eq!(registry.entry_count(), 1);
933
934 let _current = tracked.into_current();
935
936 assert_eq!(registry.entry_count(), 0);
937 }
938
939 #[test]
940 fn cloned_tracked_wrapper_is_detached_from_original_registry_entry() {
941 let registry = Arc::new(TrackingRegistry::default());
942 let mut original = Tracked::from_loaded(DummyEntity);
943 original.attach_registry(Arc::clone(®istry));
944 original.mark_modified();
945
946 let clone = original.clone();
947
948 assert_eq!(registry.entry_count(), 1);
949 assert_eq!(clone.state(), EntityState::Modified);
950
951 drop(clone);
952
953 assert_eq!(registry.entry_count(), 1);
954 assert_eq!(registry.registrations()[0].state, EntityState::Modified);
955 }
956
957 #[test]
958 fn mutable_access_transitions_loaded_entity_to_modified() {
959 let mut tracked = Tracked::from_loaded(String::from("Ana"));
960
961 tracked.push_str(" Maria");
962
963 assert_eq!(tracked.state(), EntityState::Modified);
964 assert_eq!(tracked.original(), "Ana");
965 assert_eq!(tracked.current(), "Ana Maria");
966 }
967
968 #[test]
969 fn current_mut_transitions_loaded_entity_to_modified() {
970 let mut tracked = Tracked::from_loaded(String::from("Luis"));
971
972 tracked.current_mut().push_str(" Alberto");
973
974 assert_eq!(tracked.state(), EntityState::Modified);
975 assert_eq!(tracked.original(), "Luis");
976 assert_eq!(tracked.current(), "Luis Alberto");
977 }
978
979 #[test]
980 fn explicit_mark_modified_transitions_unchanged_only() {
981 let mut loaded = Tracked::from_loaded(String::from("Ana"));
982 loaded.mark_modified();
983
984 let mut added = Tracked::from_added(String::from("Luis"));
985 added.mark_modified();
986
987 let mut deleted = Tracked::from_loaded(String::from("Maria"));
988 deleted.mark_deleted();
989 deleted.mark_modified();
990
991 assert_eq!(loaded.state(), EntityState::Modified);
992 assert_eq!(added.state(), EntityState::Added);
993 assert_eq!(deleted.state(), EntityState::Deleted);
994 }
995
996 #[test]
997 fn explicit_mark_deleted_transitions_wrapper_to_deleted() {
998 let mut tracked = Tracked::from_loaded(String::from("Ana"));
999
1000 tracked.mark_deleted();
1001
1002 assert_eq!(tracked.state(), EntityState::Deleted);
1003 }
1004
1005 #[test]
1006 fn explicit_mark_unchanged_accepts_current_snapshot() {
1007 let mut tracked = Tracked::from_loaded(String::from("Ana"));
1008 tracked.current_mut().push_str(" Maria");
1009
1010 tracked.mark_unchanged();
1011
1012 assert_eq!(tracked.state(), EntityState::Unchanged);
1013 assert_eq!(tracked.original(), "Ana Maria");
1014 assert_eq!(tracked.current(), "Ana Maria");
1015 }
1016
1017 #[test]
1018 fn explicit_mark_unchanged_restores_deleted_wrapper_with_current_snapshot() {
1019 let mut tracked = Tracked::from_loaded(String::from("Ana"));
1020 tracked.current_mut().push_str(" Maria");
1021 tracked.mark_deleted();
1022
1023 tracked.mark_unchanged();
1024
1025 assert_eq!(tracked.state(), EntityState::Unchanged);
1026 assert_eq!(tracked.original(), "Ana Maria");
1027 assert_eq!(tracked.current(), "Ana Maria");
1028 }
1029
1030 #[test]
1031 fn explicit_mark_unchanged_on_registered_wrapper_updates_registry_state() {
1032 let registry = Arc::new(TrackingRegistry::default());
1033 let mut tracked = Tracked::from_loaded(DummyEntity);
1034 tracked.attach_registry(Arc::clone(®istry));
1035 tracked.mark_deleted();
1036
1037 tracked.mark_unchanged();
1038
1039 assert_eq!(tracked.state(), EntityState::Unchanged);
1040 assert_eq!(registry.entry_count(), 1);
1041 assert_eq!(registry.registrations()[0].state, EntityState::Unchanged);
1042 }
1043
1044 #[test]
1045 fn mark_deleted_transitions_any_registered_entity_to_deleted() {
1046 let registry = Arc::new(TrackingRegistry::default());
1047 let mut tracked = Tracked::from_loaded(DummyEntity);
1048 tracked.attach_registry(Arc::clone(®istry));
1049
1050 tracked.mark_deleted();
1051
1052 assert_eq!(tracked.state(), EntityState::Deleted);
1053 assert_eq!(registry.registrations()[0].state, EntityState::Deleted);
1054 }
1055
1056 #[test]
1057 fn mark_deleted_on_added_registered_entry_cancels_pending_insert() {
1058 let registry = Arc::new(TrackingRegistry::default());
1059 let mut tracked = Tracked::from_added(DummyEntity);
1060 tracked.attach_registry_added(Arc::clone(®istry));
1061
1062 tracked.mark_deleted();
1063
1064 assert_eq!(tracked.state(), EntityState::Deleted);
1065 assert_eq!(registry.entry_count(), 0);
1066 }
1067
1068 #[test]
1069 fn mutable_access_keeps_added_state_for_new_entities() {
1070 let mut tracked = Tracked::from_added(String::from("Maria"));
1071
1072 tracked.push_str(" Fernanda");
1073
1074 assert_eq!(tracked.state(), EntityState::Added);
1075 assert_eq!(tracked.original(), "Maria");
1076 assert_eq!(tracked.current(), "Maria Fernanda");
1077 }
1078
1079 #[test]
1080 fn tracking_registry_records_loaded_entities() {
1081 let registry = Arc::new(TrackingRegistry::default());
1082 let mut tracked = Tracked::from_loaded(DummyEntity);
1083
1084 tracked
1085 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(7))
1086 .unwrap();
1087
1088 assert_eq!(registry.entry_count(), 1);
1089 assert_eq!(
1090 registry.registrations(),
1091 vec![TrackedEntityRegistration {
1092 entity_rust_name: "DummyEntity",
1093 state: EntityState::Unchanged,
1094 }]
1095 );
1096 }
1097
1098 #[test]
1099 fn tracking_registry_records_added_entities() {
1100 let registry = Arc::new(TrackingRegistry::default());
1101 let mut tracked = Tracked::from_added(DummyEntity);
1102
1103 tracked.attach_registry(Arc::clone(®istry));
1104
1105 assert_eq!(registry.entry_count(), 1);
1106 assert_eq!(
1107 registry.registrations(),
1108 vec![TrackedEntityRegistration {
1109 entity_rust_name: "DummyEntity",
1110 state: EntityState::Added,
1111 }]
1112 );
1113 }
1114
1115 #[test]
1116 fn tracking_registry_rejects_duplicate_loaded_identity() {
1117 let registry = Arc::new(TrackingRegistry::default());
1118 let mut first = Tracked::from_loaded(DummyEntity);
1119 let mut second = Tracked::from_loaded(DummyEntity);
1120
1121 first
1122 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(7))
1123 .unwrap();
1124 let error = second
1125 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(7))
1126 .unwrap_err();
1127
1128 assert_eq!(registry.entry_count(), 1);
1129 assert!(error.message().contains("already tracked"));
1130 }
1131
1132 #[test]
1133 fn duplicate_loaded_identity_error_leaves_rejected_wrapper_detached() {
1134 let registry = Arc::new(TrackingRegistry::default());
1135 let mut first = Tracked::from_loaded(DummyEntity);
1136
1137 first
1138 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(7))
1139 .unwrap();
1140
1141 {
1142 let mut duplicate = Tracked::from_loaded(DummyEntity);
1143 let error = duplicate
1144 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(7))
1145 .unwrap_err();
1146
1147 assert!(error.message().contains("already tracked"));
1148 assert_eq!(duplicate.state(), EntityState::Unchanged);
1149 assert_eq!(registry.entry_count(), 1);
1150 }
1151
1152 assert_eq!(registry.entry_count(), 1);
1153 assert_eq!(registry.registrations()[0].state, EntityState::Unchanged);
1154 }
1155
1156 #[test]
1157 fn tracking_registry_scopes_loaded_identity_by_rust_type() {
1158 let registry = Arc::new(TrackingRegistry::default());
1159 let mut first = Tracked::from_loaded(DummyEntity);
1160 let mut second = Tracked::from_loaded(DummyEntityAlias);
1161
1162 first
1163 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(7))
1164 .unwrap();
1165 second
1166 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(7))
1167 .unwrap();
1168
1169 assert_eq!(registry.entry_count(), 2);
1170 }
1171
1172 #[test]
1173 fn tracking_registry_allows_multiple_added_entities_with_temporary_identities() {
1174 let registry = Arc::new(TrackingRegistry::default());
1175 let mut first = Tracked::from_added(DummyEntity);
1176 let mut second = Tracked::from_added(DummyEntity);
1177
1178 first.attach_registry_added(Arc::clone(®istry));
1179 second.attach_registry_added(Arc::clone(®istry));
1180
1181 assert_eq!(registry.entry_count(), 2);
1182 }
1183
1184 #[test]
1185 fn tracking_registry_updates_temporary_identity_to_persisted_identity() {
1186 let registry = Arc::new(TrackingRegistry::default());
1187 let mut tracked = Tracked::from_added(DummyEntity);
1188 tracked.attach_registry_added(Arc::clone(®istry));
1189
1190 registry
1191 .update_persisted_identity::<DummyEntity>(
1192 tracked.registration_id.expect("registered"),
1193 SqlValue::I64(11),
1194 )
1195 .unwrap();
1196
1197 let mut duplicate = Tracked::from_loaded(DummyEntity);
1198 let error = duplicate
1199 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(11))
1200 .unwrap_err();
1201
1202 assert!(error.message().contains("already tracked"));
1203 }
1204
1205 #[test]
1206 fn tracking_registry_rejects_persisted_identity_update_collision_without_mutating_entry() {
1207 let registry = Arc::new(TrackingRegistry::default());
1208 let mut existing = Tracked::from_loaded(DummyEntity);
1209 let mut pending = Tracked::from_added(DummyEntity);
1210
1211 existing
1212 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(11))
1213 .unwrap();
1214 pending.attach_registry_added(Arc::clone(®istry));
1215
1216 let pending_registration = pending.registration_id.expect("registered pending entity");
1217 let error = registry
1218 .update_persisted_identity::<DummyEntity>(pending_registration, SqlValue::I64(11))
1219 .unwrap_err();
1220
1221 assert!(error.message().contains("already tracked"));
1222 assert_eq!(registry.entry_count(), 2);
1223
1224 let mut duplicate = Tracked::from_loaded(DummyEntity);
1225 let duplicate_error = duplicate
1226 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(11))
1227 .unwrap_err();
1228 assert!(duplicate_error.message().contains("already tracked"));
1229
1230 registry
1231 .update_persisted_identity::<DummyEntity>(pending_registration, SqlValue::I64(12))
1232 .unwrap();
1233
1234 let mut second_duplicate = Tracked::from_loaded(DummyEntity);
1235 let second_duplicate_error = second_duplicate
1236 .attach_registry_loaded(Arc::clone(®istry), SqlValue::I64(12))
1237 .unwrap_err();
1238 assert!(second_duplicate_error.message().contains("already tracked"));
1239 }
1240
1241 #[test]
1242 fn tracking_registry_rejects_persisted_identity_update_for_missing_registration() {
1243 let registry = TrackingRegistry::default();
1244
1245 let error = registry
1246 .update_persisted_identity::<DummyEntity>(99, SqlValue::I64(11))
1247 .unwrap_err();
1248
1249 assert_eq!(error.message(), "tracked entity registration was not found");
1250 }
1251
1252 #[test]
1253 fn tracking_registry_clear_removes_all_entries() {
1254 let registry = Arc::new(TrackingRegistry::default());
1255 let mut first = Tracked::from_added(DummyEntity);
1256 let mut second = Tracked::from_added(DummyEntity);
1257 first.attach_registry_added(Arc::clone(®istry));
1258 second.attach_registry_added(Arc::clone(®istry));
1259
1260 registry.clear();
1261
1262 assert_eq!(registry.entry_count(), 0);
1263 assert!(registry.registrations().is_empty());
1264 }
1265
1266 #[test]
1267 fn detach_registry_unregisters_without_dropping_wrapper() {
1268 let registry = Arc::new(TrackingRegistry::default());
1269 let mut tracked = Tracked::from_loaded(DummyEntity);
1270 tracked.attach_registry(Arc::clone(®istry));
1271
1272 tracked.detach_registry();
1273
1274 assert_eq!(registry.entry_count(), 0);
1275 assert_eq!(tracked.state(), EntityState::Unchanged);
1276 }
1277
1278 #[test]
1279 fn public_detach_is_idempotent_and_keeps_visible_state() {
1280 let registry = Arc::new(TrackingRegistry::default());
1281 let mut tracked = Tracked::from_loaded(DummyEntity);
1282 tracked.attach_registry(Arc::clone(®istry));
1283 tracked.mark_deleted();
1284
1285 tracked.detach();
1286 tracked.detach();
1287
1288 assert_eq!(registry.entry_count(), 0);
1289 assert_eq!(tracked.state(), EntityState::Deleted);
1290 }
1291
1292 #[test]
1293 fn public_detach_unregisters_without_resetting_state() {
1294 let registry = Arc::new(TrackingRegistry::default());
1295 let mut tracked = Tracked::from_loaded(DummyEntity);
1296 tracked.attach_registry(Arc::clone(®istry));
1297 tracked.mark_modified();
1298
1299 tracked.detach();
1300
1301 assert_eq!(registry.entry_count(), 0);
1302 assert_eq!(tracked.state(), EntityState::Modified);
1303 }
1304
1305 #[test]
1306 fn tracking_registry_unregister_missing_registration_is_noop() {
1307 let registry = Arc::new(TrackingRegistry::default());
1308 let mut tracked = Tracked::from_loaded(DummyEntity);
1309 tracked.attach_registry(Arc::clone(®istry));
1310
1311 registry.unregister(99);
1312
1313 assert_eq!(registry.entry_count(), 1);
1314 assert_eq!(registry.registrations()[0].state, EntityState::Unchanged);
1315 }
1316
1317 #[test]
1318 fn dropping_tracked_entity_unregisters_it_from_registry() {
1319 let registry = Arc::new(TrackingRegistry::default());
1320
1321 {
1322 let mut tracked = Tracked::from_loaded(DummyEntity);
1323 tracked.attach_registry(Arc::clone(®istry));
1324 assert_eq!(registry.entry_count(), 1);
1325 }
1326
1327 assert_eq!(registry.entry_count(), 0);
1328 }
1329
1330 #[test]
1331 fn save_changes_plan_orders_added_parents_before_children() {
1332 let plan = save_changes_operation_plan(&[
1333 &ORDER_ITEM_METADATA,
1334 &DUMMY_ENTITY_METADATA,
1335 &ORDER_METADATA,
1336 ])
1337 .unwrap();
1338
1339 assert_eq!(plan.added_order(), &[1, 2, 0]);
1340 assert_eq!(plan.modified_order(), &[1, 2, 0]);
1341 }
1342
1343 #[test]
1344 fn save_changes_plan_orders_deleted_children_before_parents() {
1345 let plan = save_changes_operation_plan(&[
1346 &ORDER_ITEM_METADATA,
1347 &DUMMY_ENTITY_METADATA,
1348 &ORDER_METADATA,
1349 ])
1350 .unwrap();
1351
1352 assert_eq!(plan.deleted_order(), &[0, 2, 1]);
1353 }
1354
1355 #[test]
1356 fn save_changes_plan_preserves_context_order_without_dependencies() {
1357 let plan = save_changes_operation_plan(&[&ORDER_METADATA, &DUMMY_ENTITY_METADATA]).unwrap();
1358
1359 assert_eq!(plan.added_order(), &[0, 1]);
1360 assert_eq!(plan.modified_order(), &[0, 1]);
1361 assert_eq!(plan.deleted_order(), &[1, 0]);
1362 }
1363
1364 #[test]
1365 fn save_changes_plan_ignores_foreign_keys_to_entities_outside_context() {
1366 let plan =
1367 save_changes_operation_plan(&[&ORDER_ITEM_METADATA, &DUMMY_ENTITY_METADATA]).unwrap();
1368
1369 assert_eq!(plan.added_order(), &[0, 1]);
1370 assert_eq!(plan.modified_order(), &[0, 1]);
1371 assert_eq!(plan.deleted_order(), &[1, 0]);
1372 }
1373
1374 #[test]
1375 fn save_changes_plan_ignores_simple_self_references() {
1376 let plan =
1377 save_changes_operation_plan(&[&CATEGORY_METADATA, &DUMMY_ENTITY_METADATA]).unwrap();
1378
1379 assert_eq!(plan.added_order(), &[0, 1]);
1380 assert_eq!(plan.modified_order(), &[0, 1]);
1381 assert_eq!(plan.deleted_order(), &[1, 0]);
1382 }
1383
1384 #[test]
1385 fn save_changes_plan_rejects_foreign_key_cycles() {
1386 let error =
1387 save_changes_operation_plan(&[&CYCLE_A_METADATA, &CYCLE_B_METADATA]).unwrap_err();
1388
1389 assert!(error.message().contains("foreign-key cycle"));
1390 }
1391}