1use std::{
81 collections::{BTreeSet, HashMap, HashSet},
82 sync::Arc,
83};
84
85use dashmap::DashMap;
86use hyphae::Gettable;
87use tracing::{debug, info, trace};
88
89use super::{CellServerCtx, persister::PersistError};
90use crate::{
91 core::item::AnyItem,
92 relationship::{
93 ArrayExtractor, ArrayRemover, EnsureForDependency, EntityFactory, FkExtractor, Relation,
94 iter_relations,
95 },
96};
97
98#[derive(Clone)]
100struct BelongsToLookup {
101 id: u64,
102 local_type: &'static str,
103 foreign_type: &'static str,
104 extract_fk: FkExtractor,
105}
106
107#[derive(Clone)]
109struct OwnsManyLookup {
110 local_type: &'static str,
111 foreign_type: &'static str,
112 extract_ids: ArrayExtractor,
113 remove_id: ArrayRemover,
114}
115
116#[derive(Clone)]
118struct EnsureForLookup {
119 local_type: &'static str,
120 dependencies: Vec<EnsureForDependency>,
121 make_entity: EntityFactory,
122}
123
124pub struct RelationshipManager {
134 belongs_to_by_foreign: HashMap<&'static str, Vec<BelongsToLookup>>,
137
138 belongs_to_by_local: HashMap<&'static str, Vec<BelongsToLookup>>,
141
142 owns_many_by_local: HashMap<&'static str, Vec<OwnsManyLookup>>,
145
146 owns_many_by_foreign: HashMap<&'static str, Vec<OwnsManyLookup>>,
149
150 ensure_for_by_dependency: HashMap<&'static str, Vec<EnsureForLookup>>,
153
154 belongs_to_children_by_parent: DashMap<u64, DashMap<Arc<str>, BTreeSet<Arc<str>>>>,
156
157 belongs_to_parent_by_child: DashMap<u64, DashMap<Arc<str>, Arc<str>>>,
159}
160
161impl RelationshipManager {
162 pub fn new() -> Self {
164 trace!("RelationshipManager: Initializing from inventory");
165
166 let mut belongs_to_by_foreign: HashMap<&'static str, Vec<BelongsToLookup>> = HashMap::new();
167 let mut belongs_to_by_local: HashMap<&'static str, Vec<BelongsToLookup>> = HashMap::new();
168 let mut owns_many_by_local: HashMap<&'static str, Vec<OwnsManyLookup>> = HashMap::new();
169 let mut owns_many_by_foreign: HashMap<&'static str, Vec<OwnsManyLookup>> = HashMap::new();
170 let mut ensure_for_by_dependency: HashMap<&'static str, Vec<EnsureForLookup>> =
171 HashMap::new();
172
173 let mut next_belongs_to_id = 1u64;
174 for registration in iter_relations() {
175 match ®istration.relation {
176 Relation::BelongsTo {
177 local_type,
178 foreign_type,
179 extract_fk,
180 ..
181 } => {
182 trace!(
183 "RelationshipManager: Registered BelongsTo {} -> {}",
184 local_type, foreign_type
185 );
186 let lookup = BelongsToLookup {
187 id: next_belongs_to_id,
188 local_type,
189 foreign_type,
190 extract_fk: *extract_fk,
191 };
192 next_belongs_to_id += 1;
193 belongs_to_by_foreign
194 .entry(foreign_type)
195 .or_default()
196 .push(lookup.clone());
197 belongs_to_by_local
198 .entry(local_type)
199 .or_default()
200 .push(lookup);
201 }
202 Relation::OwnsMany {
203 local_type,
204 foreign_type,
205 extract_ids,
206 remove_id,
207 ..
208 } => {
209 trace!(
210 "RelationshipManager: Registered OwnsMany {} ->> {}",
211 local_type, foreign_type
212 );
213 let lookup = OwnsManyLookup {
214 local_type,
215 foreign_type,
216 extract_ids: *extract_ids,
217 remove_id: *remove_id,
218 };
219 owns_many_by_local
220 .entry(local_type)
221 .or_default()
222 .push(lookup.clone());
223 owns_many_by_foreign
224 .entry(foreign_type)
225 .or_default()
226 .push(lookup);
227 }
228 Relation::EnsureFor {
229 local_type,
230 dependencies,
231 make_entity,
232 ..
233 } => {
234 trace!(
235 "RelationshipManager: Registered EnsureFor {} for {:?}",
236 local_type,
237 dependencies
238 .iter()
239 .map(|d| d.foreign_type)
240 .collect::<Vec<_>>()
241 );
242 let deps: Vec<_> = dependencies.to_vec();
243
244 for dep in dependencies.iter() {
246 ensure_for_by_dependency
247 .entry(dep.foreign_type)
248 .or_default()
249 .push(EnsureForLookup {
250 local_type,
251 dependencies: deps.clone(),
252 make_entity: *make_entity,
253 });
254 }
255 }
256 }
257 }
258
259 let relation_count =
260 belongs_to_by_foreign.len() + owns_many_by_local.len() + ensure_for_by_dependency.len();
261 trace!(
262 "RelationshipManager: {} relation types indexed",
263 relation_count
264 );
265
266 Self {
267 belongs_to_by_foreign,
268 belongs_to_by_local,
269 owns_many_by_local,
270 owns_many_by_foreign,
271 ensure_for_by_dependency,
272 belongs_to_children_by_parent: DashMap::new(),
273 belongs_to_parent_by_child: DashMap::new(),
274 }
275 }
276
277 pub fn forward_set(
282 &self,
283 item: Arc<dyn AnyItem>,
284 ctx: &CellServerCtx,
285 ) -> Result<(), PersistError> {
286 let item_type = item.entity_type();
287
288 if let Some(lookups) = self.belongs_to_by_local.get(item_type) {
289 for lookup in lookups {
290 self.index_belongs_to_child(lookup, &item);
291 }
292 }
293
294 if self.ensure_for_by_dependency.contains_key(item_type) {
296 self.handle_ensure_for(&item, ctx)?;
297 }
298
299 Ok(())
300 }
301
302 pub fn forward_del(
309 &self,
310 item: Arc<dyn AnyItem>,
311 ctx: &CellServerCtx,
312 ) -> Result<(), PersistError> {
313 self.handle_belongs_to_cascade(&item, ctx)?;
315
316 self.handle_owns_many_parent_delete(&item, ctx)?;
318
319 self.handle_owns_many_child_delete(&item, ctx)?;
321
322 if let Some(lookups) = self.belongs_to_by_local.get(item.entity_type()) {
323 for lookup in lookups {
324 self.remove_belongs_to_child(lookup, &item.id());
325 }
326 }
327
328 Ok(())
329 }
330
331 pub fn forward_del_batch(
337 &self,
338 items: &[Arc<dyn AnyItem>],
339 ctx: &CellServerCtx,
340 ) -> Result<(), PersistError> {
341 if items.is_empty() {
342 return Ok(());
343 }
344
345 self.handle_belongs_to_cascade_batch(items, ctx)?;
346 self.handle_owns_many_parent_delete_batch(items, ctx)?;
347
348 for item in items {
349 self.handle_owns_many_child_delete(item, ctx)?;
350
351 if let Some(lookups) = self.belongs_to_by_local.get(item.entity_type()) {
352 for lookup in lookups {
353 self.remove_belongs_to_child(lookup, &item.id());
354 }
355 }
356 }
357
358 Ok(())
359 }
360
361 pub fn establish_relations(&self, ctx: &CellServerCtx) -> Result<(), PersistError> {
368 info!("RelationshipManager: Establishing relations on startup");
369 trace!(
370 "RelationshipManager: BelongsTo relations by local: {:?}",
371 self.belongs_to_by_local.keys().collect::<Vec<_>>()
372 );
373 debug!(
374 "RelationshipManager: OwnsMany relations by local: {:?}",
375 self.owns_many_by_local.keys().collect::<Vec<_>>()
376 );
377
378 self.cleanup_belongs_to_orphans(ctx)?;
380
381 self.cleanup_owns_many_orphans(ctx)?;
383
384 self.initialize_ensure_for(ctx)?;
386
387 info!("RelationshipManager: Relations established");
388 Ok(())
389 }
390
391 fn handle_belongs_to_cascade(
397 &self,
398 item: &Arc<dyn AnyItem>,
399 ctx: &CellServerCtx,
400 ) -> Result<(), PersistError> {
401 let item_type = item.entity_type();
402 let Some(lookups) = self.belongs_to_by_foreign.get(item_type) else {
403 return Ok(());
404 };
405
406 let parent_id = item.id();
407
408 for lookup in lookups {
409 let children = self.find_children_by_fk(ctx, lookup, &parent_id);
411 if children.is_empty() {
412 continue;
413 }
414
415 trace!(
416 "RelationshipManager: Cascade delete batch {} count={} (parent {} deleted)",
417 lookup.local_type,
418 children.len(),
419 parent_id
420 );
421 self.publish_del_cascade_batch(ctx, &children)?;
422 }
423
424 Ok(())
425 }
426
427 fn handle_belongs_to_cascade_batch(
428 &self,
429 items: &[Arc<dyn AnyItem>],
430 ctx: &CellServerCtx,
431 ) -> Result<(), PersistError> {
432 let Some(first) = items.first() else {
433 return Ok(());
434 };
435 let item_type = first.entity_type();
436 let Some(lookups) = self.belongs_to_by_foreign.get(item_type) else {
437 return Ok(());
438 };
439
440 let parent_ids: Vec<Arc<str>> = items.iter().map(|item| item.id()).collect();
441
442 for lookup in lookups {
443 let mut children_by_id: HashMap<Arc<str>, Arc<dyn AnyItem>> = HashMap::new();
444 for parent_id in &parent_ids {
445 for child in self.find_children_by_fk(ctx, lookup, parent_id) {
446 children_by_id.entry(child.id()).or_insert(child);
447 }
448 }
449
450 if children_by_id.is_empty() {
451 continue;
452 }
453
454 let children: Vec<_> = children_by_id.into_values().collect();
455 trace!(
456 "RelationshipManager: Cascade delete batch {} count={} ({} parents deleted)",
457 lookup.local_type,
458 children.len(),
459 parent_ids.len()
460 );
461 self.publish_del_cascade_batch(ctx, &children)?;
462 }
463
464 Ok(())
465 }
466
467 fn find_children_by_fk(
469 &self,
470 ctx: &CellServerCtx,
471 lookup: &BelongsToLookup,
472 parent_id: &str,
473 ) -> Vec<Arc<dyn AnyItem>> {
474 self.ensure_belongs_to_index_loaded(ctx, lookup);
475 if let Some(parent_map) = self.belongs_to_children_by_parent.get(&lookup.id) {
476 let store = ctx.registry.get_or_create(lookup.local_type);
477 let Some(child_ids) = parent_map.get(parent_id) else {
478 return Vec::new();
479 };
480 return child_ids
481 .iter()
482 .filter_map(|child_id| store.get_value(child_id))
483 .collect();
484 }
485
486 let store = ctx.registry.get_or_create(lookup.local_type);
487 store
488 .entries()
489 .get()
490 .into_iter()
491 .filter(|(_, item)| {
492 (lookup.extract_fk)(item.as_any())
493 .map(|fk| fk.as_ref() == parent_id)
494 .unwrap_or(false)
495 })
496 .map(|(_, item)| item)
497 .collect()
498 }
499
500 fn index_belongs_to_child(&self, lookup: &BelongsToLookup, item: &Arc<dyn AnyItem>) {
501 let child_id = item.id();
502 self.remove_belongs_to_child(lookup, &child_id);
503
504 let Some(parent_id) = (lookup.extract_fk)(item.as_any()) else {
505 return;
506 };
507
508 self.belongs_to_parent_by_child
509 .entry(lookup.id)
510 .or_default()
511 .insert(child_id.clone(), parent_id.clone());
512 self.belongs_to_children_by_parent
513 .entry(lookup.id)
514 .or_default()
515 .entry(parent_id)
516 .or_default()
517 .insert(child_id);
518 }
519
520 fn remove_belongs_to_child(&self, lookup: &BelongsToLookup, child_id: &Arc<str>) {
521 let Some(parent_map) = self.belongs_to_parent_by_child.get(&lookup.id) else {
522 return;
523 };
524 let Some((_, parent_id)) = parent_map.remove(child_id) else {
525 return;
526 };
527
528 let Some(children_by_parent) = self.belongs_to_children_by_parent.get(&lookup.id) else {
529 return;
530 };
531 let should_remove_parent = children_by_parent
532 .get_mut(parent_id.as_ref())
533 .map(|mut child_ids| {
534 child_ids.remove(child_id);
535 child_ids.is_empty()
536 })
537 .unwrap_or(false);
538
539 if should_remove_parent {
540 children_by_parent.remove(parent_id.as_ref());
541 }
542 }
543
544 fn ensure_belongs_to_index_loaded(&self, ctx: &CellServerCtx, lookup: &BelongsToLookup) {
545 if self.belongs_to_parent_by_child.contains_key(&lookup.id) {
546 return;
547 }
548
549 let child_index = DashMap::<Arc<str>, Arc<str>>::new();
550 let parent_index = DashMap::<Arc<str>, BTreeSet<Arc<str>>>::new();
551 let store = ctx.registry.get_or_create(lookup.local_type);
552
553 for (_, item) in store.snapshot() {
554 let Some(parent_id) = (lookup.extract_fk)(item.as_any()) else {
555 continue;
556 };
557 let child_id = item.id();
558 child_index.insert(child_id.clone(), parent_id.clone());
559 parent_index.entry(parent_id).or_default().insert(child_id);
560 }
561
562 let _ = self
563 .belongs_to_parent_by_child
564 .insert(lookup.id, child_index);
565 let _ = self
566 .belongs_to_children_by_parent
567 .insert(lookup.id, parent_index);
568 }
569
570 fn handle_owns_many_parent_delete(
572 &self,
573 item: &Arc<dyn AnyItem>,
574 ctx: &CellServerCtx,
575 ) -> Result<(), PersistError> {
576 let item_type = item.entity_type();
577 let Some(lookups) = self.owns_many_by_local.get(item_type) else {
578 return Ok(());
579 };
580
581 for lookup in lookups {
582 let child_ids = match (lookup.extract_ids)(item.as_any()) {
584 Some(ids) => ids,
585 None => continue,
586 };
587
588 if child_ids.is_empty() {
589 continue;
590 }
591
592 let mut children = Vec::new();
593 for child_id in &child_ids {
594 if self.get_by_id(ctx, lookup.foreign_type, child_id).is_some()
595 && let Some(child) = self.get_by_id(ctx, lookup.foreign_type, child_id)
596 {
597 children.push(child);
598 }
599 }
600
601 if children.is_empty() {
602 continue;
603 }
604
605 trace!(
606 "RelationshipManager: Cascade delete owned batch {} count={}",
607 lookup.foreign_type,
608 children.len()
609 );
610 self.publish_del_cascade_batch(ctx, &children)?;
611 }
612
613 Ok(())
614 }
615
616 fn handle_owns_many_parent_delete_batch(
617 &self,
618 items: &[Arc<dyn AnyItem>],
619 ctx: &CellServerCtx,
620 ) -> Result<(), PersistError> {
621 let Some(first) = items.first() else {
622 return Ok(());
623 };
624 let item_type = first.entity_type();
625 let Some(lookups) = self.owns_many_by_local.get(item_type) else {
626 return Ok(());
627 };
628
629 for lookup in lookups {
630 let mut child_ids = BTreeSet::new();
631 for item in items {
632 if let Some(ids) = (lookup.extract_ids)(item.as_any()) {
633 child_ids.extend(ids);
634 }
635 }
636
637 if child_ids.is_empty() {
638 continue;
639 }
640
641 let mut children = Vec::new();
642 for child_id in &child_ids {
643 if let Some(child) = self.get_by_id(ctx, lookup.foreign_type, child_id) {
644 children.push(child);
645 }
646 }
647
648 if children.is_empty() {
649 continue;
650 }
651
652 trace!(
653 "RelationshipManager: Cascade delete owned batch {} count={} ({} parents deleted)",
654 lookup.foreign_type,
655 children.len(),
656 items.len()
657 );
658 self.publish_del_cascade_batch(ctx, &children)?;
659 }
660
661 Ok(())
662 }
663
664 fn handle_owns_many_child_delete(
666 &self,
667 item: &Arc<dyn AnyItem>,
668 ctx: &CellServerCtx,
669 ) -> Result<(), PersistError> {
670 let item_type = item.entity_type();
671 let Some(lookups) = self.owns_many_by_foreign.get(item_type) else {
672 return Ok(());
673 };
674
675 let child_id = item.id();
676
677 for lookup in lookups {
678 let parents = self.find_parents_containing(ctx, lookup, &child_id);
680 let mut updates = Vec::new();
681
682 for parent_item in parents {
683 if let Some(updated_parent) = (lookup.remove_id)(parent_item.as_any(), &child_id) {
685 trace!(
686 "RelationshipManager: Updating {} {} to remove child {}",
687 lookup.local_type,
688 parent_item.id(),
689 child_id
690 );
691 updates.push(updated_parent);
692 }
693 }
694
695 if !updates.is_empty() {
696 self.publish_set_cascade_batch(ctx, &updates)?;
697 }
698 }
699
700 Ok(())
701 }
702
703 fn find_parents_containing(
705 &self,
706 ctx: &CellServerCtx,
707 lookup: &OwnsManyLookup,
708 child_id: &str,
709 ) -> Vec<Arc<dyn AnyItem>> {
710 let store = ctx.registry.get_or_create(lookup.local_type);
711 store
712 .entries()
713 .get()
714 .into_iter()
715 .filter(|(_, item)| {
716 (lookup.extract_ids)(item.as_any())
717 .map(|ids| ids.iter().any(|id| id.as_ref() == child_id))
718 .unwrap_or(false)
719 })
720 .map(|(_, item)| item)
721 .collect()
722 }
723
724 fn handle_ensure_for(
726 &self,
727 item: &Arc<dyn AnyItem>,
728 ctx: &CellServerCtx,
729 ) -> Result<(), PersistError> {
730 let item_type = item.entity_type();
731 let Some(lookups) = self.ensure_for_by_dependency.get(item_type) else {
732 return Ok(());
733 };
734
735 for lookup in lookups {
736 let combinations = self.get_dependency_combinations(ctx, &lookup.dependencies);
738
739 let store = ctx.registry.get_or_create(lookup.local_type);
742 let existing_items = store.snapshot();
743
744 for combo in combinations {
745 let existing =
747 Self::find_ensure_for_entity_in(&existing_items, &lookup.dependencies, &combo);
748
749 if existing.is_none() {
750 let entity = (lookup.make_entity)(&combo);
752
753 trace!(
754 "RelationshipManager: Creating ensured {} for {:?}",
755 lookup.local_type, combo
756 );
757
758 self.publish_set_cascade(ctx, lookup.local_type, entity)?;
759 }
760 }
761 }
762
763 Ok(())
764 }
765
766 fn cleanup_belongs_to_orphans(&self, ctx: &CellServerCtx) -> Result<(), PersistError> {
783 trace!(
784 "RelationshipManager: cleanup_belongs_to_orphans - checking {} child types",
785 self.belongs_to_by_local.len()
786 );
787
788 for (child_type, lookups) in &self.belongs_to_by_local {
789 trace!(
790 "RelationshipManager: Checking BelongsTo orphans for child type '{}' ({} lookups)",
791 child_type,
792 lookups.len()
793 );
794
795 for lookup in lookups {
796 let parents = self.get_all_items(ctx, lookup.foreign_type);
798 let parent_ids: HashSet<Arc<str>> = parents.iter().map(|p| p.id()).collect();
799
800 trace!(
801 "RelationshipManager: {} -> {}: Found {} parents in store",
802 child_type,
803 lookup.foreign_type,
804 parents.len()
805 );
806
807 let children = self.get_all_items(ctx, child_type);
809 trace!(
810 "RelationshipManager: {} -> {}: Found {} children in store",
811 child_type,
812 lookup.foreign_type,
813 children.len()
814 );
815
816 let mut orphan_count = 0;
817 let mut valid_count = 0;
818 let mut no_fk_count = 0;
819
820 for child in &children {
821 if let Some(fk_value) = (lookup.extract_fk)(child.as_any()) {
823 if !parent_ids.contains(&fk_value) {
824 debug!(
825 "RelationshipManager: ORPHAN {} {} has FK '{}' but parent {} not found (have {} parent IDs)",
826 child_type,
827 child.id(),
828 fk_value,
829 lookup.foreign_type,
830 parent_ids.len()
831 );
832 self.publish_del_cascade(ctx, child_type, &child.id())?;
833 orphan_count += 1;
834 } else {
835 valid_count += 1;
836 }
837 } else {
838 trace!(
839 "RelationshipManager: {} {} - extract_fk returned None",
840 child_type,
841 child.id()
842 );
843 no_fk_count += 1;
844 }
845 }
846
847 trace!(
848 "RelationshipManager: {} -> {}: {} orphans deleted, {} valid, {} no FK",
849 child_type, lookup.foreign_type, orphan_count, valid_count, no_fk_count
850 );
851 }
852 }
853
854 Ok(())
855 }
856
857 fn cleanup_owns_many_orphans(&self, ctx: &CellServerCtx) -> Result<(), PersistError> {
859 trace!(
860 "RelationshipManager: cleanup_owns_many_orphans - checking {} parent types",
861 self.owns_many_by_local.len()
862 );
863
864 for (parent_type, lookups) in &self.owns_many_by_local {
865 trace!(
866 "RelationshipManager: Checking OwnsMany orphans for parent type '{}' ({} lookups)",
867 parent_type,
868 lookups.len()
869 );
870
871 for lookup in lookups {
872 let parents = self.get_all_items(ctx, parent_type);
874 let mut referenced_ids: HashSet<Arc<str>> = HashSet::new();
875
876 trace!(
877 "RelationshipManager: {} ->> {}: Found {} parents in store",
878 parent_type,
879 lookup.foreign_type,
880 parents.len()
881 );
882
883 let mut parents_with_ids = 0;
884 let mut parents_no_ids = 0;
885 for parent in &parents {
886 if let Some(ids) = (lookup.extract_ids)(parent.as_any()) {
887 if !ids.is_empty() {
888 parents_with_ids += 1;
889 }
890 referenced_ids.extend(ids);
891 } else {
892 parents_no_ids += 1;
893 }
894 }
895
896 trace!(
897 "RelationshipManager: {} ->> {}: {} parents have child IDs, {} have no IDs, {} total referenced child IDs",
898 parent_type,
899 lookup.foreign_type,
900 parents_with_ids,
901 parents_no_ids,
902 referenced_ids.len()
903 );
904
905 let children = self.get_all_items(ctx, lookup.foreign_type);
907 trace!(
908 "RelationshipManager: {} ->> {}: Found {} children in store",
909 parent_type,
910 lookup.foreign_type,
911 children.len()
912 );
913
914 let mut orphan_count = 0;
915 let mut valid_count = 0;
916
917 for child in children {
918 let child_id = child.id();
919 if !referenced_ids.contains(&child_id) {
920 debug!(
921 "RelationshipManager: ORPHAN {} {} not referenced by any {} (have {} referenced IDs)",
922 lookup.foreign_type,
923 child_id,
924 parent_type,
925 referenced_ids.len()
926 );
927 self.publish_del_cascade(ctx, lookup.foreign_type, &child_id)?;
928 orphan_count += 1;
929 } else {
930 valid_count += 1;
931 }
932 }
933
934 if orphan_count > 0 {
935 info!(
936 "RelationshipManager: {} ->> {}: {} orphans deleted, {} valid",
937 parent_type, lookup.foreign_type, orphan_count, valid_count
938 );
939 } else {
940 trace!(
941 "RelationshipManager: {} ->> {}: {} orphans deleted, {} valid",
942 parent_type, lookup.foreign_type, orphan_count, valid_count
943 );
944 }
945 }
946 }
947
948 Ok(())
949 }
950
951 fn initialize_ensure_for(&self, ctx: &CellServerCtx) -> Result<(), PersistError> {
953 let mut processed: HashSet<&'static str> = HashSet::new();
955
956 for lookups in self.ensure_for_by_dependency.values() {
957 for lookup in lookups {
958 if processed.contains(lookup.local_type) {
959 continue;
960 }
961 processed.insert(lookup.local_type);
962
963 let combinations = self.get_dependency_combinations(ctx, &lookup.dependencies);
965
966 let store = ctx.registry.get_or_create(lookup.local_type);
968 let existing_items = store.snapshot();
969
970 let mut created_count = 0;
971
972 for combo in combinations {
973 let existing = Self::find_ensure_for_entity_in(
975 &existing_items,
976 &lookup.dependencies,
977 &combo,
978 );
979
980 if existing.is_none() {
981 let entity = (lookup.make_entity)(&combo);
983 self.publish_set_cascade(ctx, lookup.local_type, entity)?;
984 created_count += 1;
985 }
986 }
987
988 if created_count > 0 {
989 info!(
990 "RelationshipManager: Created {} {} entities via EnsureFor",
991 created_count, lookup.local_type
992 );
993 }
994 }
995 }
996
997 Ok(())
998 }
999
1000 fn get_by_id(
1006 &self,
1007 ctx: &CellServerCtx,
1008 entity_type: &str,
1009 id: &str,
1010 ) -> Option<Arc<dyn AnyItem>> {
1011 let store = ctx.registry.get_or_create(entity_type);
1012 store.get_value(&id.into())
1013 }
1014
1015 fn get_all_items(&self, ctx: &CellServerCtx, entity_type: &str) -> Vec<Arc<dyn AnyItem>> {
1017 let store = ctx.registry.get_or_create(entity_type);
1018 store.snapshot().into_iter().map(|(_, item)| item).collect()
1019 }
1020
1021 fn get_dependency_combinations(
1023 &self,
1024 ctx: &CellServerCtx,
1025 dependencies: &[EnsureForDependency],
1026 ) -> Vec<Vec<Arc<str>>> {
1027 if dependencies.is_empty() {
1028 return vec![];
1029 }
1030
1031 let mut dep_ids: Vec<Vec<Arc<str>>> = Vec::new();
1033
1034 for dep in dependencies {
1035 let items = self.get_all_items(ctx, dep.foreign_type);
1036 let ids: Vec<Arc<str>> = items.iter().map(|item| item.id()).collect();
1037 dep_ids.push(ids);
1038 }
1039
1040 self.cartesian_product(&dep_ids)
1042 }
1043
1044 fn cartesian_product(&self, sets: &[Vec<Arc<str>>]) -> Vec<Vec<Arc<str>>> {
1046 if sets.is_empty() {
1047 return vec![];
1048 }
1049
1050 let mut result = vec![vec![]];
1051
1052 for set in sets {
1053 let mut new_result = Vec::new();
1054 for existing in &result {
1055 for item in set {
1056 let mut new_combo = existing.clone();
1057 new_combo.push(item.clone());
1058 new_result.push(new_combo);
1059 }
1060 }
1061 result = new_result;
1062 }
1063
1064 result
1065 }
1066
1067 fn find_ensure_for_entity_in(
1070 items: &[(Arc<str>, Arc<dyn AnyItem>)],
1071 dependencies: &[EnsureForDependency],
1072 combo: &[Arc<str>],
1073 ) -> Option<Arc<dyn AnyItem>> {
1074 if dependencies.is_empty() || combo.is_empty() {
1075 return None;
1076 }
1077
1078 items.iter().find_map(|(_, item)| {
1079 let all_match = dependencies
1081 .iter()
1082 .zip(combo.iter())
1083 .all(|(dep, expected_id)| {
1084 (dep.extract_fk)(item.as_any())
1085 .map(|fk| fk == *expected_id)
1086 .unwrap_or(false)
1087 });
1088
1089 if all_match { Some(item.clone()) } else { None }
1090 })
1091 }
1092
1093 fn publish_set_cascade(
1101 &self,
1102 ctx: &CellServerCtx,
1103 _entity_type: &str,
1104 item: Arc<dyn AnyItem>,
1105 ) -> Result<(), PersistError> {
1106 let item = if item.server_owner().is_none() {
1108 item.bake_server_owner(&ctx.host_id.to_string())
1109 .unwrap_or(item)
1110 } else {
1111 item
1112 };
1113
1114 ctx.set_dyn_with_origin(item, super::Origin::Cascade)
1115 }
1116
1117 fn publish_set_cascade_batch(
1118 &self,
1119 ctx: &CellServerCtx,
1120 items: &[Arc<dyn AnyItem>],
1121 ) -> Result<(), PersistError> {
1122 ctx.batch_set_dyn_with_origin(items, super::Origin::Cascade)
1123 }
1124
1125 fn publish_del_cascade(
1129 &self,
1130 ctx: &CellServerCtx,
1131 entity_type: &str,
1132 id: &str,
1133 ) -> Result<(), PersistError> {
1134 let id_arc: Arc<str> = id.into();
1136 if let Some(item) = ctx.registry.get_or_create(entity_type).get_value(&id_arc) {
1137 debug!(
1138 "RelationshipManager: publish_del_cascade {} {} - entity found, deleting",
1139 entity_type, id
1140 );
1141 ctx.del_dyn_with_origin(item, super::Origin::Cascade)?;
1142 } else {
1143 trace!(
1144 "RelationshipManager: publish_del_cascade {} {} - entity NOT found in store",
1145 entity_type, id
1146 );
1147 }
1148
1149 Ok(())
1150 }
1151
1152 fn publish_del_cascade_batch(
1153 &self,
1154 ctx: &CellServerCtx,
1155 items: &[Arc<dyn AnyItem>],
1156 ) -> Result<(), PersistError> {
1157 if items.is_empty() {
1158 return Ok(());
1159 }
1160
1161 ctx.batch_del_dyn_with_origin(items, super::Origin::Cascade)
1162 }
1163}
1164
1165impl Default for RelationshipManager {
1166 fn default() -> Self {
1167 Self::new()
1168 }
1169}
1170
1171#[cfg(test)]
1172mod tests {
1173 use super::*;
1174
1175 #[test]
1176 fn test_relationship_manager_creation() {
1177 let manager = RelationshipManager::new();
1178
1179 let _ = manager.belongs_to_by_foreign.len();
1183 let _ = manager.owns_many_by_local.len();
1184 }
1185
1186 #[test]
1187 fn test_cartesian_product() {
1188 let manager = RelationshipManager::new();
1189
1190 let sets = vec![
1191 vec![Arc::from("a"), Arc::from("b")],
1192 vec![Arc::from("1"), Arc::from("2")],
1193 ];
1194
1195 let product = manager.cartesian_product(&sets);
1196
1197 assert_eq!(product.len(), 4);
1198 assert!(product.contains(&vec![Arc::from("a"), Arc::from("1")]));
1199 assert!(product.contains(&vec![Arc::from("a"), Arc::from("2")]));
1200 assert!(product.contains(&vec![Arc::from("b"), Arc::from("1")]));
1201 assert!(product.contains(&vec![Arc::from("b"), Arc::from("2")]));
1202 }
1203
1204 #[test]
1205 fn test_cartesian_product_empty() {
1206 let manager = RelationshipManager::new();
1207
1208 let sets: Vec<Vec<Arc<str>>> = vec![];
1209 let product = manager.cartesian_product(&sets);
1210 assert!(product.is_empty());
1211 }
1212}
1213
1214#[cfg(test)]
1215mod cascade_tests {
1216 use std::sync::Arc;
1224
1225 use uuid::Uuid;
1226
1227 use self::node::CascadeNode;
1228 use crate::{
1229 hyphae::Gettable,
1230 search::SearchIndex,
1231 server::{CellServerCtx, HandlerRegistry, RelationshipManager, persister::PersisterRouter},
1232 store::StoreRegistry,
1233 test_util::scheduler_test_serial,
1234 };
1235
1236 mod node {
1239 use crate::prelude::*;
1240
1241 #[myko_item]
1244 pub struct CascadeNode {
1245 #[belongs_to(CascadeNode)]
1246 pub parent_id: CascadeNodeId,
1247 pub name: String,
1248 }
1249 }
1250
1251 fn make_ctx() -> (CellServerCtx, Arc<StoreRegistry>) {
1252 let registry = Arc::new(StoreRegistry::new());
1253 let ctx = CellServerCtx::new(
1254 Uuid::new_v4(),
1255 registry.clone(),
1256 Arc::new(HandlerRegistry::new()),
1257 Arc::new(RelationshipManager::new()),
1258 Arc::new(PersisterRouter::default()),
1259 Arc::new(SearchIndex::new()),
1260 Arc::new(dashmap::DashMap::new()),
1261 None,
1262 None,
1263 );
1264 (ctx, registry)
1265 }
1266
1267 fn make_node(id: &str, parent_id: &str) -> CascadeNode {
1268 CascadeNode {
1269 id: id.into(),
1270 parent_id: parent_id.into(),
1271 name: format!("node-{id}"),
1272 }
1273 }
1274
1275 fn exists(registry: &StoreRegistry, id: &str) -> bool {
1276 registry
1277 .get("CascadeNode")
1278 .and_then(|store| store.get(&Arc::<str>::from(id)).get())
1279 .is_some()
1280 }
1281
1282 #[test]
1285 fn del_cascade_descends_to_grandchildren() {
1286 let _serial = scheduler_test_serial();
1287 let (ctx, registry) = make_ctx();
1288
1289 ctx.set(&make_node("root", "")).unwrap();
1291 ctx.set(&make_node("branch", "root")).unwrap();
1292 ctx.set(&make_node("leaf", "branch")).unwrap();
1293
1294 assert!(exists(®istry, "root"));
1295 assert!(exists(®istry, "branch"));
1296 assert!(exists(®istry, "leaf"));
1297
1298 ctx.del(&make_node("root", "")).unwrap();
1299
1300 assert!(!exists(®istry, "root"), "root deleted");
1301 assert!(!exists(®istry, "branch"), "direct child deleted");
1302 assert!(
1303 !exists(®istry, "leaf"),
1304 "grandchild deleted at runtime (Fix #1)"
1305 );
1306 }
1307
1308 #[test]
1317 fn del_cascade_recursion_does_not_drop_earlier_diffs_in_same_store() {
1318 let _serial = scheduler_test_serial();
1319 let (ctx, registry) = make_ctx();
1320
1321 ctx.set(&make_node("root", "")).unwrap();
1322 ctx.set(&make_node("branch", "root")).unwrap();
1323 ctx.set(&make_node("leaf", "branch")).unwrap();
1324 ctx.set(&make_node("island", "")).unwrap();
1325
1326 let store = registry.get_or_create("CascadeNode");
1327 let seen: Arc<std::sync::Mutex<Vec<String>>> = Arc::new(std::sync::Mutex::new(Vec::new()));
1328 let seen_for_closure = seen.clone();
1329 let _guard = store.subscribe_diffs(move |diff| {
1330 seen_for_closure.lock().unwrap().push(format!("{diff:?}"));
1331 });
1332 seen.lock().unwrap().clear();
1335
1336 let root_item: Arc<dyn crate::core::item::AnyItem> = Arc::new(make_node("root", ""));
1341 let island_item: Arc<dyn crate::core::item::AnyItem> = Arc::new(make_node("island", ""));
1342 ctx.batch_del_dyn(&[root_item, island_item]).unwrap();
1343
1344 assert!(!exists(®istry, "root"));
1345 assert!(!exists(®istry, "branch"), "direct child deleted");
1346 assert!(!exists(®istry, "leaf"), "grandchild deleted");
1347 assert!(!exists(®istry, "island"));
1348
1349 let seen = seen.lock().unwrap();
1350 assert_eq!(
1351 seen.len(),
1352 3,
1353 "expected 3 separate diffs (root+island reduce, branch cascade, leaf cascade), not coalesced: {:?}",
1354 *seen
1355 );
1356 }
1357
1358 #[test]
1361 fn del_cascade_terminates_on_cycle() {
1362 let _serial = scheduler_test_serial();
1363 let (ctx, registry) = make_ctx();
1364
1365 ctx.set(&make_node("a", "b")).unwrap();
1366 ctx.set(&make_node("b", "a")).unwrap();
1367
1368 ctx.del(&make_node("a", "b")).unwrap();
1369
1370 assert!(!exists(®istry, "a"), "a deleted");
1371 assert!(
1372 !exists(®istry, "b"),
1373 "b deleted via the cycle, then terminated"
1374 );
1375 }
1376}