1use std::{
81 collections::{BTreeSet, HashMap, HashSet},
82 sync::Arc,
83};
84
85use dashmap::DashMap;
86use hyphae::Gettable;
87use log::{debug, info, trace};
88
89use super::{CellServerCtx, persister::PersistError};
90use crate::{
91 core::item::AnyItem,
92 event::EventOptions,
93 relationship::{
94 ArrayExtractor, ArrayRemover, EnsureForDependency, EntityFactory, FkExtractor, Relation,
95 iter_relations,
96 },
97};
98
99#[derive(Clone)]
101struct BelongsToLookup {
102 id: u64,
103 local_type: &'static str,
104 foreign_type: &'static str,
105 extract_fk: FkExtractor,
106}
107
108#[derive(Clone)]
110struct OwnsManyLookup {
111 local_type: &'static str,
112 foreign_type: &'static str,
113 extract_ids: ArrayExtractor,
114 remove_id: ArrayRemover,
115}
116
117#[derive(Clone)]
119struct EnsureForLookup {
120 local_type: &'static str,
121 dependencies: Vec<EnsureForDependency>,
122 make_entity: EntityFactory,
123}
124
125pub struct RelationshipManager {
135 belongs_to_by_foreign: HashMap<&'static str, Vec<BelongsToLookup>>,
138
139 belongs_to_by_local: HashMap<&'static str, Vec<BelongsToLookup>>,
142
143 owns_many_by_local: HashMap<&'static str, Vec<OwnsManyLookup>>,
146
147 owns_many_by_foreign: HashMap<&'static str, Vec<OwnsManyLookup>>,
150
151 ensure_for_by_dependency: HashMap<&'static str, Vec<EnsureForLookup>>,
154
155 belongs_to_children_by_parent: DashMap<u64, DashMap<Arc<str>, BTreeSet<Arc<str>>>>,
157
158 belongs_to_parent_by_child: DashMap<u64, DashMap<Arc<str>, Arc<str>>>,
160}
161
162impl RelationshipManager {
163 pub fn new() -> Self {
165 trace!("RelationshipManager: Initializing from inventory");
166
167 let mut belongs_to_by_foreign: HashMap<&'static str, Vec<BelongsToLookup>> = HashMap::new();
168 let mut belongs_to_by_local: HashMap<&'static str, Vec<BelongsToLookup>> = HashMap::new();
169 let mut owns_many_by_local: HashMap<&'static str, Vec<OwnsManyLookup>> = HashMap::new();
170 let mut owns_many_by_foreign: HashMap<&'static str, Vec<OwnsManyLookup>> = HashMap::new();
171 let mut ensure_for_by_dependency: HashMap<&'static str, Vec<EnsureForLookup>> =
172 HashMap::new();
173
174 let mut next_belongs_to_id = 1u64;
175 for registration in iter_relations() {
176 match ®istration.relation {
177 Relation::BelongsTo {
178 local_type,
179 foreign_type,
180 extract_fk,
181 ..
182 } => {
183 trace!(
184 "RelationshipManager: Registered BelongsTo {} -> {}",
185 local_type, foreign_type
186 );
187 let lookup = BelongsToLookup {
188 id: next_belongs_to_id,
189 local_type,
190 foreign_type,
191 extract_fk: *extract_fk,
192 };
193 next_belongs_to_id += 1;
194 belongs_to_by_foreign
195 .entry(foreign_type)
196 .or_default()
197 .push(lookup.clone());
198 belongs_to_by_local
199 .entry(local_type)
200 .or_default()
201 .push(lookup);
202 }
203 Relation::OwnsMany {
204 local_type,
205 foreign_type,
206 extract_ids,
207 remove_id,
208 ..
209 } => {
210 trace!(
211 "RelationshipManager: Registered OwnsMany {} ->> {}",
212 local_type, foreign_type
213 );
214 let lookup = OwnsManyLookup {
215 local_type,
216 foreign_type,
217 extract_ids: *extract_ids,
218 remove_id: *remove_id,
219 };
220 owns_many_by_local
221 .entry(local_type)
222 .or_default()
223 .push(lookup.clone());
224 owns_many_by_foreign
225 .entry(foreign_type)
226 .or_default()
227 .push(lookup);
228 }
229 Relation::EnsureFor {
230 local_type,
231 dependencies,
232 make_entity,
233 ..
234 } => {
235 trace!(
236 "RelationshipManager: Registered EnsureFor {} for {:?}",
237 local_type,
238 dependencies
239 .iter()
240 .map(|d| d.foreign_type)
241 .collect::<Vec<_>>()
242 );
243 let deps: Vec<_> = dependencies.to_vec();
244
245 for dep in dependencies.iter() {
247 ensure_for_by_dependency
248 .entry(dep.foreign_type)
249 .or_default()
250 .push(EnsureForLookup {
251 local_type,
252 dependencies: deps.clone(),
253 make_entity: *make_entity,
254 });
255 }
256 }
257 }
258 }
259
260 let relation_count =
261 belongs_to_by_foreign.len() + owns_many_by_local.len() + ensure_for_by_dependency.len();
262 trace!(
263 "RelationshipManager: {} relation types indexed",
264 relation_count
265 );
266
267 Self {
268 belongs_to_by_foreign,
269 belongs_to_by_local,
270 owns_many_by_local,
271 owns_many_by_foreign,
272 ensure_for_by_dependency,
273 belongs_to_children_by_parent: DashMap::new(),
274 belongs_to_parent_by_child: DashMap::new(),
275 }
276 }
277
278 pub fn forward_set(
283 &self,
284 item: Arc<dyn AnyItem>,
285 ctx: &CellServerCtx,
286 ) -> Result<(), PersistError> {
287 let item_type = item.entity_type();
288
289 if let Some(lookups) = self.belongs_to_by_local.get(item_type) {
290 for lookup in lookups {
291 self.index_belongs_to_child(lookup, &item);
292 }
293 }
294
295 if self.ensure_for_by_dependency.contains_key(item_type) {
297 self.handle_ensure_for(&item, ctx)?;
298 }
299
300 Ok(())
301 }
302
303 pub fn forward_del(
310 &self,
311 item: Arc<dyn AnyItem>,
312 ctx: &CellServerCtx,
313 ) -> Result<(), PersistError> {
314 self.handle_belongs_to_cascade(&item, ctx)?;
316
317 self.handle_owns_many_parent_delete(&item, ctx)?;
319
320 self.handle_owns_many_child_delete(&item, ctx)?;
322
323 if let Some(lookups) = self.belongs_to_by_local.get(item.entity_type()) {
324 for lookup in lookups {
325 self.remove_belongs_to_child(lookup, &item.id());
326 }
327 }
328
329 Ok(())
330 }
331
332 pub fn forward_del_batch(
338 &self,
339 items: &[Arc<dyn AnyItem>],
340 ctx: &CellServerCtx,
341 ) -> Result<(), PersistError> {
342 if items.is_empty() {
343 return Ok(());
344 }
345
346 self.handle_belongs_to_cascade_batch(items, ctx)?;
347 self.handle_owns_many_parent_delete_batch(items, ctx)?;
348
349 for item in items {
350 self.handle_owns_many_child_delete(item, ctx)?;
351
352 if let Some(lookups) = self.belongs_to_by_local.get(item.entity_type()) {
353 for lookup in lookups {
354 self.remove_belongs_to_child(lookup, &item.id());
355 }
356 }
357 }
358
359 Ok(())
360 }
361
362 pub fn establish_relations(&self, ctx: &CellServerCtx) -> Result<(), PersistError> {
369 info!("RelationshipManager: Establishing relations on startup");
370 trace!(
371 "RelationshipManager: BelongsTo relations by local: {:?}",
372 self.belongs_to_by_local.keys().collect::<Vec<_>>()
373 );
374 debug!(
375 "RelationshipManager: OwnsMany relations by local: {:?}",
376 self.owns_many_by_local.keys().collect::<Vec<_>>()
377 );
378
379 self.cleanup_belongs_to_orphans(ctx)?;
381
382 self.cleanup_owns_many_orphans(ctx)?;
384
385 self.initialize_ensure_for(ctx)?;
387
388 info!("RelationshipManager: Relations established");
389 Ok(())
390 }
391
392 fn handle_belongs_to_cascade(
398 &self,
399 item: &Arc<dyn AnyItem>,
400 ctx: &CellServerCtx,
401 ) -> Result<(), PersistError> {
402 let item_type = item.entity_type();
403 let Some(lookups) = self.belongs_to_by_foreign.get(item_type) else {
404 return Ok(());
405 };
406
407 let parent_id = item.id();
408
409 for lookup in lookups {
410 let children = self.find_children_by_fk(ctx, lookup, &parent_id);
412 if children.is_empty() {
413 continue;
414 }
415
416 trace!(
417 "RelationshipManager: Cascade delete batch {} count={} (parent {} deleted)",
418 lookup.local_type,
419 children.len(),
420 &parent_id
421 );
422 self.publish_del_cascade_batch(ctx, &children)?;
423 }
424
425 Ok(())
426 }
427
428 fn handle_belongs_to_cascade_batch(
429 &self,
430 items: &[Arc<dyn AnyItem>],
431 ctx: &CellServerCtx,
432 ) -> Result<(), PersistError> {
433 let Some(first) = items.first() else {
434 return Ok(());
435 };
436 let item_type = first.entity_type();
437 let Some(lookups) = self.belongs_to_by_foreign.get(item_type) else {
438 return Ok(());
439 };
440
441 let parent_ids: Vec<Arc<str>> = items.iter().map(|item| item.id()).collect();
442
443 for lookup in lookups {
444 let mut children_by_id: HashMap<Arc<str>, Arc<dyn AnyItem>> = HashMap::new();
445 for parent_id in &parent_ids {
446 for child in self.find_children_by_fk(ctx, lookup, parent_id) {
447 children_by_id.entry(child.id()).or_insert(child);
448 }
449 }
450
451 if children_by_id.is_empty() {
452 continue;
453 }
454
455 let children: Vec<_> = children_by_id.into_values().collect();
456 trace!(
457 "RelationshipManager: Cascade delete batch {} count={} ({} parents deleted)",
458 lookup.local_type,
459 children.len(),
460 parent_ids.len()
461 );
462 self.publish_del_cascade_batch(ctx, &children)?;
463 }
464
465 Ok(())
466 }
467
468 fn find_children_by_fk(
470 &self,
471 ctx: &CellServerCtx,
472 lookup: &BelongsToLookup,
473 parent_id: &str,
474 ) -> Vec<Arc<dyn AnyItem>> {
475 self.ensure_belongs_to_index_loaded(ctx, lookup);
476 if let Some(parent_map) = self.belongs_to_children_by_parent.get(&lookup.id) {
477 let store = ctx.registry.get_or_create(lookup.local_type);
478 let Some(child_ids) = parent_map.get(parent_id) else {
479 return Vec::new();
480 };
481 return child_ids
482 .iter()
483 .filter_map(|child_id| store.get_value(child_id))
484 .collect();
485 }
486
487 let store = ctx.registry.get_or_create(lookup.local_type);
488 store
489 .entries()
490 .get()
491 .into_iter()
492 .filter(|(_, item)| {
493 (lookup.extract_fk)(item.as_any())
494 .map(|fk| fk.as_ref() == parent_id)
495 .unwrap_or(false)
496 })
497 .map(|(_, item)| item)
498 .collect()
499 }
500
501 fn index_belongs_to_child(&self, lookup: &BelongsToLookup, item: &Arc<dyn AnyItem>) {
502 let child_id = item.id();
503 self.remove_belongs_to_child(lookup, &child_id);
504
505 let Some(parent_id) = (lookup.extract_fk)(item.as_any()) else {
506 return;
507 };
508
509 self.belongs_to_parent_by_child
510 .entry(lookup.id)
511 .or_default()
512 .insert(child_id.clone(), parent_id.clone());
513 self.belongs_to_children_by_parent
514 .entry(lookup.id)
515 .or_default()
516 .entry(parent_id)
517 .or_default()
518 .insert(child_id);
519 }
520
521 fn remove_belongs_to_child(&self, lookup: &BelongsToLookup, child_id: &Arc<str>) {
522 let Some(parent_map) = self.belongs_to_parent_by_child.get(&lookup.id) else {
523 return;
524 };
525 let Some((_, parent_id)) = parent_map.remove(child_id) else {
526 return;
527 };
528
529 let Some(children_by_parent) = self.belongs_to_children_by_parent.get(&lookup.id) else {
530 return;
531 };
532 let should_remove_parent = children_by_parent
533 .get_mut(parent_id.as_ref())
534 .map(|mut child_ids| {
535 child_ids.remove(child_id);
536 child_ids.is_empty()
537 })
538 .unwrap_or(false);
539
540 if should_remove_parent {
541 children_by_parent.remove(parent_id.as_ref());
542 }
543 }
544
545 fn ensure_belongs_to_index_loaded(&self, ctx: &CellServerCtx, lookup: &BelongsToLookup) {
546 if self.belongs_to_parent_by_child.contains_key(&lookup.id) {
547 return;
548 }
549
550 let child_index = DashMap::<Arc<str>, Arc<str>>::new();
551 let parent_index = DashMap::<Arc<str>, BTreeSet<Arc<str>>>::new();
552 let store = ctx.registry.get_or_create(lookup.local_type);
553
554 for (_, item) in store.snapshot() {
555 let Some(parent_id) = (lookup.extract_fk)(item.as_any()) else {
556 continue;
557 };
558 let child_id = item.id();
559 child_index.insert(child_id.clone(), parent_id.clone());
560 parent_index.entry(parent_id).or_default().insert(child_id);
561 }
562
563 let _ = self
564 .belongs_to_parent_by_child
565 .insert(lookup.id, child_index);
566 let _ = self
567 .belongs_to_children_by_parent
568 .insert(lookup.id, parent_index);
569 }
570
571 fn handle_owns_many_parent_delete(
573 &self,
574 item: &Arc<dyn AnyItem>,
575 ctx: &CellServerCtx,
576 ) -> Result<(), PersistError> {
577 let item_type = item.entity_type();
578 let Some(lookups) = self.owns_many_by_local.get(item_type) else {
579 return Ok(());
580 };
581
582 for lookup in lookups {
583 let child_ids = match (lookup.extract_ids)(item.as_any()) {
585 Some(ids) => ids,
586 None => continue,
587 };
588
589 if child_ids.is_empty() {
590 continue;
591 }
592
593 let mut children = Vec::new();
594 for child_id in &child_ids {
595 if self.get_by_id(ctx, lookup.foreign_type, child_id).is_some()
596 && let Some(child) = self.get_by_id(ctx, lookup.foreign_type, child_id)
597 {
598 children.push(child);
599 }
600 }
601
602 if children.is_empty() {
603 continue;
604 }
605
606 trace!(
607 "RelationshipManager: Cascade delete owned batch {} count={}",
608 lookup.foreign_type,
609 children.len()
610 );
611 self.publish_del_cascade_batch(ctx, &children)?;
612 }
613
614 Ok(())
615 }
616
617 fn handle_owns_many_parent_delete_batch(
618 &self,
619 items: &[Arc<dyn AnyItem>],
620 ctx: &CellServerCtx,
621 ) -> Result<(), PersistError> {
622 let Some(first) = items.first() else {
623 return Ok(());
624 };
625 let item_type = first.entity_type();
626 let Some(lookups) = self.owns_many_by_local.get(item_type) else {
627 return Ok(());
628 };
629
630 for lookup in lookups {
631 let mut child_ids = BTreeSet::new();
632 for item in items {
633 if let Some(ids) = (lookup.extract_ids)(item.as_any()) {
634 child_ids.extend(ids);
635 }
636 }
637
638 if child_ids.is_empty() {
639 continue;
640 }
641
642 let mut children = Vec::new();
643 for child_id in &child_ids {
644 if let Some(child) = self.get_by_id(ctx, lookup.foreign_type, child_id) {
645 children.push(child);
646 }
647 }
648
649 if children.is_empty() {
650 continue;
651 }
652
653 trace!(
654 "RelationshipManager: Cascade delete owned batch {} count={} ({} parents deleted)",
655 lookup.foreign_type,
656 children.len(),
657 items.len()
658 );
659 self.publish_del_cascade_batch(ctx, &children)?;
660 }
661
662 Ok(())
663 }
664
665 fn handle_owns_many_child_delete(
667 &self,
668 item: &Arc<dyn AnyItem>,
669 ctx: &CellServerCtx,
670 ) -> Result<(), PersistError> {
671 let item_type = item.entity_type();
672 let Some(lookups) = self.owns_many_by_foreign.get(item_type) else {
673 return Ok(());
674 };
675
676 let child_id = item.id();
677
678 for lookup in lookups {
679 let parents = self.find_parents_containing(ctx, lookup, &child_id);
681 let mut updates = Vec::new();
682
683 for parent_item in parents {
684 if let Some(updated_parent) = (lookup.remove_id)(parent_item.as_any(), &child_id) {
686 trace!(
687 "RelationshipManager: Updating {} {} to remove child {}",
688 lookup.local_type,
689 parent_item.id(),
690 child_id
691 );
692 updates.push(updated_parent);
693 }
694 }
695
696 if !updates.is_empty() {
697 self.publish_set_cascade_batch(ctx, &updates)?;
698 }
699 }
700
701 Ok(())
702 }
703
704 fn find_parents_containing(
706 &self,
707 ctx: &CellServerCtx,
708 lookup: &OwnsManyLookup,
709 child_id: &str,
710 ) -> Vec<Arc<dyn AnyItem>> {
711 let store = ctx.registry.get_or_create(lookup.local_type);
712 store
713 .entries()
714 .get()
715 .into_iter()
716 .filter(|(_, item)| {
717 (lookup.extract_ids)(item.as_any())
718 .map(|ids| ids.iter().any(|id| id.as_ref() == child_id))
719 .unwrap_or(false)
720 })
721 .map(|(_, item)| item)
722 .collect()
723 }
724
725 fn handle_ensure_for(
727 &self,
728 item: &Arc<dyn AnyItem>,
729 ctx: &CellServerCtx,
730 ) -> Result<(), PersistError> {
731 let item_type = item.entity_type();
732 let Some(lookups) = self.ensure_for_by_dependency.get(item_type) else {
733 return Ok(());
734 };
735
736 for lookup in lookups {
737 let combinations = self.get_dependency_combinations(ctx, &lookup.dependencies);
739
740 for combo in combinations {
741 let existing = self.find_ensure_for_entity(
743 ctx,
744 lookup.local_type,
745 &lookup.dependencies,
746 &combo,
747 );
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> {
772 trace!(
773 "RelationshipManager: cleanup_belongs_to_orphans - checking {} child types",
774 self.belongs_to_by_local.len()
775 );
776
777 for (child_type, lookups) in &self.belongs_to_by_local {
778 trace!(
779 "RelationshipManager: Checking BelongsTo orphans for child type '{}' ({} lookups)",
780 child_type,
781 lookups.len()
782 );
783
784 for lookup in lookups {
785 let parents = self.get_all_items(ctx, lookup.foreign_type);
787 let parent_ids: HashSet<Arc<str>> = parents.iter().map(|p| p.id()).collect();
788
789 trace!(
790 "RelationshipManager: {} -> {}: Found {} parents in store",
791 child_type,
792 lookup.foreign_type,
793 parents.len()
794 );
795
796 let children = self.get_all_items(ctx, child_type);
798 trace!(
799 "RelationshipManager: {} -> {}: Found {} children in store",
800 child_type,
801 lookup.foreign_type,
802 children.len()
803 );
804
805 let mut orphan_count = 0;
806 let mut valid_count = 0;
807 let mut no_fk_count = 0;
808
809 for child in &children {
810 if let Some(fk_value) = (lookup.extract_fk)(child.as_any()) {
812 if !parent_ids.contains(&fk_value) {
813 debug!(
814 "RelationshipManager: ORPHAN {} {} has FK '{}' but parent {} not found (have {} parent IDs)",
815 child_type,
816 child.id(),
817 fk_value,
818 lookup.foreign_type,
819 parent_ids.len()
820 );
821 self.publish_del_cascade(ctx, child_type, &child.id())?;
822 orphan_count += 1;
823 } else {
824 valid_count += 1;
825 }
826 } else {
827 trace!(
828 "RelationshipManager: {} {} - extract_fk returned None",
829 child_type,
830 child.id()
831 );
832 no_fk_count += 1;
833 }
834 }
835
836 trace!(
837 "RelationshipManager: {} -> {}: {} orphans deleted, {} valid, {} no FK",
838 child_type, lookup.foreign_type, orphan_count, valid_count, no_fk_count
839 );
840 }
841 }
842
843 Ok(())
844 }
845
846 fn cleanup_owns_many_orphans(&self, ctx: &CellServerCtx) -> Result<(), PersistError> {
848 trace!(
849 "RelationshipManager: cleanup_owns_many_orphans - checking {} parent types",
850 self.owns_many_by_local.len()
851 );
852
853 for (parent_type, lookups) in &self.owns_many_by_local {
854 trace!(
855 "RelationshipManager: Checking OwnsMany orphans for parent type '{}' ({} lookups)",
856 parent_type,
857 lookups.len()
858 );
859
860 for lookup in lookups {
861 let parents = self.get_all_items(ctx, parent_type);
863 let mut referenced_ids: HashSet<Arc<str>> = HashSet::new();
864
865 trace!(
866 "RelationshipManager: {} ->> {}: Found {} parents in store",
867 parent_type,
868 lookup.foreign_type,
869 parents.len()
870 );
871
872 let mut parents_with_ids = 0;
873 let mut parents_no_ids = 0;
874 for parent in &parents {
875 if let Some(ids) = (lookup.extract_ids)(parent.as_any()) {
876 if !ids.is_empty() {
877 parents_with_ids += 1;
878 }
879 referenced_ids.extend(ids);
880 } else {
881 parents_no_ids += 1;
882 }
883 }
884
885 trace!(
886 "RelationshipManager: {} ->> {}: {} parents have child IDs, {} have no IDs, {} total referenced child IDs",
887 parent_type,
888 lookup.foreign_type,
889 parents_with_ids,
890 parents_no_ids,
891 referenced_ids.len()
892 );
893
894 let children = self.get_all_items(ctx, lookup.foreign_type);
896 trace!(
897 "RelationshipManager: {} ->> {}: Found {} children in store",
898 parent_type,
899 lookup.foreign_type,
900 children.len()
901 );
902
903 let mut orphan_count = 0;
904 let mut valid_count = 0;
905
906 for child in children {
907 let child_id = child.id();
908 if !referenced_ids.contains(&child_id) {
909 debug!(
910 "RelationshipManager: ORPHAN {} {} not referenced by any {} (have {} referenced IDs)",
911 lookup.foreign_type,
912 child_id,
913 parent_type,
914 referenced_ids.len()
915 );
916 self.publish_del_cascade(ctx, lookup.foreign_type, &child_id)?;
917 orphan_count += 1;
918 } else {
919 valid_count += 1;
920 }
921 }
922
923 if orphan_count > 0 {
924 info!(
925 "RelationshipManager: {} ->> {}: {} orphans deleted, {} valid",
926 parent_type, lookup.foreign_type, orphan_count, valid_count
927 );
928 } else {
929 trace!(
930 "RelationshipManager: {} ->> {}: {} orphans deleted, {} valid",
931 parent_type, lookup.foreign_type, orphan_count, valid_count
932 );
933 }
934 }
935 }
936
937 Ok(())
938 }
939
940 fn initialize_ensure_for(&self, ctx: &CellServerCtx) -> Result<(), PersistError> {
942 let mut processed: HashSet<&'static str> = HashSet::new();
944
945 for lookups in self.ensure_for_by_dependency.values() {
946 for lookup in lookups {
947 if processed.contains(lookup.local_type) {
948 continue;
949 }
950 processed.insert(lookup.local_type);
951
952 let combinations = self.get_dependency_combinations(ctx, &lookup.dependencies);
954
955 let mut created_count = 0;
956
957 for combo in combinations {
958 let existing = self.find_ensure_for_entity(
960 ctx,
961 lookup.local_type,
962 &lookup.dependencies,
963 &combo,
964 );
965
966 if existing.is_none() {
967 let entity = (lookup.make_entity)(&combo);
969 self.publish_set_cascade(ctx, lookup.local_type, entity)?;
970 created_count += 1;
971 }
972 }
973
974 if created_count > 0 {
975 info!(
976 "RelationshipManager: Created {} {} entities via EnsureFor",
977 created_count, lookup.local_type
978 );
979 }
980 }
981 }
982
983 Ok(())
984 }
985
986 fn get_by_id(
992 &self,
993 ctx: &CellServerCtx,
994 entity_type: &str,
995 id: &str,
996 ) -> Option<Arc<dyn AnyItem>> {
997 let store = ctx.registry.get_or_create(entity_type);
998 store.get_value(&id.into())
999 }
1000
1001 fn get_all_items(&self, ctx: &CellServerCtx, entity_type: &str) -> Vec<Arc<dyn AnyItem>> {
1003 let store = ctx.registry.get_or_create(entity_type);
1004 store
1005 .entries()
1006 .get()
1007 .into_iter()
1008 .map(|(_, item)| item)
1009 .collect()
1010 }
1011
1012 fn get_dependency_combinations(
1014 &self,
1015 ctx: &CellServerCtx,
1016 dependencies: &[EnsureForDependency],
1017 ) -> Vec<Vec<Arc<str>>> {
1018 if dependencies.is_empty() {
1019 return vec![];
1020 }
1021
1022 let mut dep_ids: Vec<Vec<Arc<str>>> = Vec::new();
1024
1025 for dep in dependencies {
1026 let items = self.get_all_items(ctx, dep.foreign_type);
1027 let ids: Vec<Arc<str>> = items.iter().map(|item| item.id()).collect();
1028 dep_ids.push(ids);
1029 }
1030
1031 self.cartesian_product(&dep_ids)
1033 }
1034
1035 fn cartesian_product(&self, sets: &[Vec<Arc<str>>]) -> Vec<Vec<Arc<str>>> {
1037 if sets.is_empty() {
1038 return vec![];
1039 }
1040
1041 let mut result = vec![vec![]];
1042
1043 for set in sets {
1044 let mut new_result = Vec::new();
1045 for existing in &result {
1046 for item in set {
1047 let mut new_combo = existing.clone();
1048 new_combo.push(item.clone());
1049 new_result.push(new_combo);
1050 }
1051 }
1052 result = new_result;
1053 }
1054
1055 result
1056 }
1057
1058 fn find_ensure_for_entity(
1060 &self,
1061 ctx: &CellServerCtx,
1062 local_type: &str,
1063 dependencies: &[EnsureForDependency],
1064 combo: &[Arc<str>],
1065 ) -> Option<Arc<dyn AnyItem>> {
1066 if dependencies.is_empty() || combo.is_empty() {
1067 return None;
1068 }
1069
1070 let store = ctx.registry.get_or_create(local_type);
1072
1073 store.entries().get().into_iter().find_map(|(_, item)| {
1074 let all_match = dependencies
1076 .iter()
1077 .zip(combo.iter())
1078 .all(|(dep, expected_id)| {
1079 (dep.extract_fk)(item.as_any())
1080 .map(|fk| fk == *expected_id)
1081 .unwrap_or(false)
1082 });
1083
1084 if all_match { Some(item) } else { None }
1085 })
1086 }
1087
1088 fn publish_set_cascade(
1096 &self,
1097 ctx: &CellServerCtx,
1098 _entity_type: &str,
1099 item: Arc<dyn AnyItem>,
1100 ) -> Result<(), PersistError> {
1101 let options = EventOptions {
1102 prevent_relationship_updates: true,
1103 ..Default::default()
1104 };
1105 ctx.set_dyn_with_options(item, Some(options))
1106 }
1107
1108 fn publish_set_cascade_batch(
1109 &self,
1110 ctx: &CellServerCtx,
1111 items: &[Arc<dyn AnyItem>],
1112 ) -> Result<(), PersistError> {
1113 let options = EventOptions {
1114 prevent_relationship_updates: true,
1115 ..Default::default()
1116 };
1117 ctx.batch_set_dyn_with_options(items, Some(options))
1118 }
1119
1120 fn publish_del_cascade(
1124 &self,
1125 ctx: &CellServerCtx,
1126 entity_type: &str,
1127 id: &str,
1128 ) -> Result<(), PersistError> {
1129 let options = EventOptions {
1130 prevent_relationship_updates: true,
1131 ..Default::default()
1132 };
1133
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_options(item, Some(options))?;
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 let options = EventOptions {
1162 prevent_relationship_updates: true,
1163 ..Default::default()
1164 };
1165 ctx.batch_del_dyn_with_options(items, Some(options))
1166 }
1167}
1168
1169impl Default for RelationshipManager {
1170 fn default() -> Self {
1171 Self::new()
1172 }
1173}
1174
1175#[cfg(test)]
1176mod tests {
1177 use super::*;
1178
1179 #[test]
1180 fn test_relationship_manager_creation() {
1181 let manager = RelationshipManager::new();
1182
1183 let _ = manager.belongs_to_by_foreign.len();
1187 let _ = manager.owns_many_by_local.len();
1188 }
1189
1190 #[test]
1191 fn test_cartesian_product() {
1192 let manager = RelationshipManager::new();
1193
1194 let sets = vec![
1195 vec![Arc::from("a"), Arc::from("b")],
1196 vec![Arc::from("1"), Arc::from("2")],
1197 ];
1198
1199 let product = manager.cartesian_product(&sets);
1200
1201 assert_eq!(product.len(), 4);
1202 assert!(product.contains(&vec![Arc::from("a"), Arc::from("1")]));
1203 assert!(product.contains(&vec![Arc::from("a"), Arc::from("2")]));
1204 assert!(product.contains(&vec![Arc::from("b"), Arc::from("1")]));
1205 assert!(product.contains(&vec![Arc::from("b"), Arc::from("2")]));
1206 }
1207
1208 #[test]
1209 fn test_cartesian_product_empty() {
1210 let manager = RelationshipManager::new();
1211
1212 let sets: Vec<Vec<Arc<str>>> = vec![];
1213 let product = manager.cartesian_product(&sets);
1214 assert!(product.is_empty());
1215 }
1216}