Skip to main content

common/direct_access/feature/
feature_repository.rs

1// Generated by Qleany v1.11.0 from common_entity_repository.tera
2
3use std::fmt::Display;
4
5use crate::{
6    database::hashmap_store::HashMapStoreSnapshot,
7    // `*_or_recover` rather than a plain `.read()/.write().unwrap()`: a panic
8    // anywhere under a table's guard poisons that lock for the rest of the
9    // process, and the release profile unwinds, so the poison is reachable in a
10    // shipped build. Recovering (and clearing) it keeps one unrelated failure
11    // from escalating into a second panic on every later access to the store.
12    database::hashmap_store::{read_or_recover, write_or_recover},
13    database::transactions::Transaction,
14    direct_access::repository_factory,
15    entities::Feature,
16    event::{DirectAccessEntity, EntityEvent, Event, EventBuffer, Origin},
17    snapshot::EntityTreeSnapshot,
18    types::EntityId,
19};
20
21use crate::direct_access::workspace::WorkspaceRelationshipField;
22use crate::error::RepositoryError;
23use serde::{Deserialize, Serialize};
24
25#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
26pub enum FeatureRelationshipField {
27    UseCases,
28}
29
30impl Display for FeatureRelationshipField {
31    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
32        write!(f, "{:?}", self)
33    }
34}
35
36pub trait FeatureTable {
37    fn create(&mut self, entity: &Feature) -> Result<Feature, RepositoryError>;
38    fn create_multi(&mut self, entities: &[Feature]) -> Result<Vec<Feature>, RepositoryError>;
39    fn get(&self, id: &EntityId) -> Result<Option<Feature>, RepositoryError>;
40    fn get_multi(&self, ids: &[EntityId]) -> Result<Vec<Option<Feature>>, RepositoryError>;
41    fn get_all(&self) -> Result<Vec<Feature>, RepositoryError>;
42    fn update(&mut self, entity: &Feature) -> Result<Feature, RepositoryError>;
43    fn update_multi(&mut self, entities: &[Feature]) -> Result<Vec<Feature>, RepositoryError>;
44    fn update_with_relationships(&mut self, entity: &Feature) -> Result<Feature, RepositoryError>;
45    fn update_with_relationships_multi(
46        &mut self,
47        entities: &[Feature],
48    ) -> Result<Vec<Feature>, RepositoryError>;
49    fn remove(&mut self, id: &EntityId) -> Result<(), RepositoryError>;
50    fn remove_multi(&mut self, ids: &[EntityId]) -> Result<(), RepositoryError>;
51    fn get_relationship(
52        &self,
53        id: &EntityId,
54        field: &FeatureRelationshipField,
55    ) -> Result<Vec<EntityId>, RepositoryError>;
56    fn get_relationship_many(
57        &self,
58        ids: &[EntityId],
59        field: &FeatureRelationshipField,
60    ) -> Result<std::collections::HashMap<EntityId, Vec<EntityId>>, RepositoryError>;
61    fn get_relationship_count(
62        &self,
63        id: &EntityId,
64        field: &FeatureRelationshipField,
65    ) -> Result<usize, RepositoryError>;
66    fn get_relationship_in_range(
67        &self,
68        id: &EntityId,
69        field: &FeatureRelationshipField,
70        offset: usize,
71        limit: usize,
72    ) -> Result<Vec<EntityId>, RepositoryError>;
73    fn get_relationships_from_right_ids(
74        &self,
75        field: &FeatureRelationshipField,
76        right_ids: &[EntityId],
77    ) -> Result<Vec<(EntityId, Vec<EntityId>)>, RepositoryError>;
78    fn set_relationship_multi(
79        &mut self,
80        field: &FeatureRelationshipField,
81        relationships: Vec<(EntityId, Vec<EntityId>)>,
82    ) -> Result<(), RepositoryError>;
83    fn set_relationship(
84        &mut self,
85        id: &EntityId,
86        field: &FeatureRelationshipField,
87        right_ids: &[EntityId],
88    ) -> Result<(), RepositoryError>;
89    fn move_relationship_ids(
90        &mut self,
91        id: &EntityId,
92        field: &FeatureRelationshipField,
93        ids_to_move: &[EntityId],
94        new_index: i32,
95    ) -> Result<Vec<EntityId>, RepositoryError>;
96}
97
98pub trait FeatureTableRO {
99    fn get(&self, id: &EntityId) -> Result<Option<Feature>, RepositoryError>;
100    fn get_multi(&self, ids: &[EntityId]) -> Result<Vec<Option<Feature>>, RepositoryError>;
101    fn get_all(&self) -> Result<Vec<Feature>, RepositoryError>;
102    fn get_relationship(
103        &self,
104        id: &EntityId,
105        field: &FeatureRelationshipField,
106    ) -> Result<Vec<EntityId>, RepositoryError>;
107    fn get_relationship_many(
108        &self,
109        ids: &[EntityId],
110        field: &FeatureRelationshipField,
111    ) -> Result<std::collections::HashMap<EntityId, Vec<EntityId>>, RepositoryError>;
112    fn get_relationship_count(
113        &self,
114        id: &EntityId,
115        field: &FeatureRelationshipField,
116    ) -> Result<usize, RepositoryError>;
117    fn get_relationship_in_range(
118        &self,
119        id: &EntityId,
120        field: &FeatureRelationshipField,
121        offset: usize,
122        limit: usize,
123    ) -> Result<Vec<EntityId>, RepositoryError>;
124    fn get_relationships_from_right_ids(
125        &self,
126        field: &FeatureRelationshipField,
127        right_ids: &[EntityId],
128    ) -> Result<Vec<(EntityId, Vec<EntityId>)>, RepositoryError>;
129}
130
131pub struct FeatureRepository<'a> {
132    table: Box<dyn FeatureTable + 'a>,
133    transaction: &'a Transaction,
134}
135
136impl<'a> FeatureRepository<'a> {
137    pub fn new(table: Box<dyn FeatureTable + 'a>, transaction: &'a Transaction) -> Self {
138        FeatureRepository { table, transaction }
139    }
140
141    pub fn create_orphan(
142        &mut self,
143        event_buffer: &mut EventBuffer,
144        entity: &Feature,
145    ) -> Result<Feature, RepositoryError> {
146        let new = self.table.create(entity)?;
147        event_buffer.push(Event {
148            origin: Origin::DirectAccess(DirectAccessEntity::Feature(EntityEvent::Created)),
149            ids: vec![new.id],
150            data: None,
151        });
152        Ok(new)
153    }
154
155    pub fn create_orphan_multi(
156        &mut self,
157        event_buffer: &mut EventBuffer,
158        entities: &[Feature],
159    ) -> Result<Vec<Feature>, RepositoryError> {
160        let new_entities = self.table.create_multi(entities)?;
161        event_buffer.push(Event {
162            origin: Origin::DirectAccess(DirectAccessEntity::Feature(EntityEvent::Created)),
163            ids: new_entities.iter().map(|e| e.id).collect(),
164            data: None,
165        });
166        Ok(new_entities)
167    }
168    pub fn create(
169        &mut self,
170        event_buffer: &mut EventBuffer,
171        entity: &Feature,
172        owner_id: EntityId,
173        index: i32,
174    ) -> Result<Feature, RepositoryError> {
175        let new = self.table.create(entity)?;
176        let created_id = new.id;
177
178        let mut relationship_ids = self.get_relationships_from_owner(&owner_id)?;
179        // Insert at index
180        if index >= 0 && (index as usize) < relationship_ids.len() {
181            relationship_ids.insert(index as usize, created_id);
182        } else {
183            relationship_ids.push(created_id);
184        }
185
186        self.set_relationships_in_owner(event_buffer, &owner_id, &relationship_ids)?;
187        event_buffer.push(Event {
188            origin: Origin::DirectAccess(DirectAccessEntity::Feature(EntityEvent::Created)),
189            ids: vec![created_id],
190            data: None,
191        });
192        Ok(new)
193    }
194
195    pub fn create_multi(
196        &mut self,
197        event_buffer: &mut EventBuffer,
198        entities: &[Feature],
199        owner_id: EntityId,
200        index: i32,
201    ) -> Result<Vec<Feature>, RepositoryError> {
202        let new_entities = self.table.create_multi(entities)?;
203        let created_ids: Vec<EntityId> = new_entities.iter().map(|e| e.id).collect();
204
205        let mut relationship_ids = self.get_relationships_from_owner(&owner_id)?;
206        if index >= 0 && (index as usize) < relationship_ids.len() {
207            for (i, id) in created_ids.iter().enumerate() {
208                relationship_ids.insert(index as usize + i, *id);
209            }
210        } else {
211            relationship_ids.extend(created_ids.iter());
212        }
213
214        self.set_relationships_in_owner(event_buffer, &owner_id, &relationship_ids)?;
215        event_buffer.push(Event {
216            origin: Origin::DirectAccess(DirectAccessEntity::Feature(EntityEvent::Created)),
217            ids: created_ids,
218            data: None,
219        });
220        Ok(new_entities)
221    }
222
223    pub fn get(&self, id: &EntityId) -> Result<Option<Feature>, RepositoryError> {
224        self.table.get(id)
225    }
226    pub fn get_multi(&self, ids: &[EntityId]) -> Result<Vec<Option<Feature>>, RepositoryError> {
227        self.table.get_multi(ids)
228    }
229    pub fn get_all(&self) -> Result<Vec<Feature>, RepositoryError> {
230        self.table.get_all()
231    }
232
233    pub fn update(
234        &mut self,
235        event_buffer: &mut EventBuffer,
236        entity: &Feature,
237    ) -> Result<Feature, RepositoryError> {
238        let updated = self.table.update(entity)?;
239        event_buffer.push(Event {
240            origin: Origin::DirectAccess(DirectAccessEntity::Feature(EntityEvent::Updated)),
241            ids: vec![updated.id],
242            data: None,
243        });
244        Ok(updated)
245    }
246
247    pub fn update_multi(
248        &mut self,
249        event_buffer: &mut EventBuffer,
250        entities: &[Feature],
251    ) -> Result<Vec<Feature>, RepositoryError> {
252        let updated = self.table.update_multi(entities)?;
253        event_buffer.push(Event {
254            origin: Origin::DirectAccess(DirectAccessEntity::Feature(EntityEvent::Updated)),
255            ids: updated.iter().map(|e| e.id).collect(),
256            data: None,
257        });
258        Ok(updated)
259    }
260
261    pub fn update_with_relationships(
262        &mut self,
263        event_buffer: &mut EventBuffer,
264        entity: &Feature,
265    ) -> Result<Feature, RepositoryError> {
266        let updated = self.table.update_with_relationships(entity)?;
267        event_buffer.push(Event {
268            origin: Origin::DirectAccess(DirectAccessEntity::Feature(EntityEvent::Updated)),
269            ids: vec![updated.id],
270            data: None,
271        });
272        Ok(updated)
273    }
274
275    pub fn update_with_relationships_multi(
276        &mut self,
277        event_buffer: &mut EventBuffer,
278        entities: &[Feature],
279    ) -> Result<Vec<Feature>, RepositoryError> {
280        let updated = self.table.update_with_relationships_multi(entities)?;
281        event_buffer.push(Event {
282            origin: Origin::DirectAccess(DirectAccessEntity::Feature(EntityEvent::Updated)),
283            ids: updated.iter().map(|e| e.id).collect(),
284            data: None,
285        });
286        Ok(updated)
287    }
288
289    pub fn remove(
290        &mut self,
291        event_buffer: &mut EventBuffer,
292        id: &EntityId,
293    ) -> Result<(), RepositoryError> {
294        let entity = match self.table.get(id)? {
295            Some(e) => e,
296            None => return Ok(()),
297        };
298        // get all strong forward relationship fields
299
300        let use_cases = entity.use_cases.clone();
301
302        // remove all strong relationships, initiating a cascade remove
303
304        repository_factory::write::create_use_case_repository(self.transaction)?
305            .remove_multi(event_buffer, &use_cases)?;
306        // Before removal, find which owner(s) reference this entity
307        let affected_owner_ids: Vec<EntityId> = {
308            let owner_repo =
309                repository_factory::write::create_workspace_repository(self.transaction)?;
310            owner_repo
311                .get_relationships_from_right_ids(&WorkspaceRelationshipField::Features, &[*id])?
312                .into_iter()
313                .map(|(owner_id, _)| owner_id)
314                .collect()
315        };
316        // Save each owner's current relationship IDs (properly ordered via get_relationships_from_owner)
317        let mut owner_rel_before: std::collections::HashMap<EntityId, Vec<EntityId>> =
318            std::collections::HashMap::new();
319        for owner_id in &affected_owner_ids {
320            owner_rel_before.insert(*owner_id, self.get_relationships_from_owner(owner_id)?);
321        }
322
323        // remove entity
324        self.table.remove(id)?;
325        event_buffer.push(Event {
326            origin: Origin::DirectAccess(DirectAccessEntity::Feature(EntityEvent::Removed)),
327            ids: vec![*id],
328            data: None,
329        });
330        // Update each affected owner's relationship to exclude removed ID (emits Updated event)
331        for owner_id in &affected_owner_ids {
332            if let Some(rel_ids) = owner_rel_before.get(owner_id) {
333                let updated: Vec<EntityId> =
334                    rel_ids.iter().copied().filter(|rid| *rid != *id).collect();
335                self.set_relationships_in_owner(event_buffer, owner_id, &updated)?;
336            }
337        }
338
339        Ok(())
340    }
341
342    pub fn remove_multi(
343        &mut self,
344        event_buffer: &mut EventBuffer,
345        ids: &[EntityId],
346    ) -> Result<(), RepositoryError> {
347        let entities = self.table.get_multi(ids)?;
348        if entities.is_empty() || entities.iter().all(|e| e.is_none()) {
349            return Ok(());
350        }
351
352        // get all strong forward relationship fields
353
354        let mut use_cases_ids: Vec<EntityId> = entities
355            .iter()
356            .flat_map(|entity| entity.as_ref().map(|entity| entity.use_cases.clone()))
357            .flatten()
358            .collect();
359        // remove duplicates
360        use_cases_ids.sort();
361        use_cases_ids.dedup();
362
363        // remove all strong relationships, initiating a cascade remove
364
365        repository_factory::write::create_use_case_repository(self.transaction)?
366            .remove_multi(event_buffer, &use_cases_ids)?;
367        // Before removal, find which owner(s) reference these entities
368        let affected_owner_ids: Vec<EntityId> = {
369            let owner_repo =
370                repository_factory::write::create_workspace_repository(self.transaction)?;
371            owner_repo
372                .get_relationships_from_right_ids(&WorkspaceRelationshipField::Features, ids)?
373                .into_iter()
374                .map(|(owner_id, _)| owner_id)
375                .collect()
376        };
377        // Save each owner's current relationship IDs (properly ordered via get_relationships_from_owner)
378        let mut owner_rel_before: std::collections::HashMap<EntityId, Vec<EntityId>> =
379            std::collections::HashMap::new();
380        for owner_id in &affected_owner_ids {
381            owner_rel_before.insert(*owner_id, self.get_relationships_from_owner(owner_id)?);
382        }
383
384        self.table.remove_multi(ids)?;
385        event_buffer.push(Event {
386            origin: Origin::DirectAccess(DirectAccessEntity::Feature(EntityEvent::Removed)),
387            ids: ids.into(),
388            data: None,
389        });
390        // Update each affected owner's relationship to exclude removed IDs (emits Updated event)
391        {
392            let removed_set: std::collections::HashSet<EntityId> = ids.iter().copied().collect();
393            for owner_id in &affected_owner_ids {
394                if let Some(rel_ids) = owner_rel_before.get(owner_id) {
395                    let updated: Vec<EntityId> = rel_ids
396                        .iter()
397                        .copied()
398                        .filter(|rid| !removed_set.contains(rid))
399                        .collect();
400                    self.set_relationships_in_owner(event_buffer, owner_id, &updated)?;
401                }
402            }
403        }
404
405        Ok(())
406    }
407    pub fn get_relationship(
408        &self,
409        id: &EntityId,
410        field: &FeatureRelationshipField,
411    ) -> Result<Vec<EntityId>, RepositoryError> {
412        self.table.get_relationship(id, field)
413    }
414    pub fn get_relationship_many(
415        &self,
416        ids: &[EntityId],
417        field: &FeatureRelationshipField,
418    ) -> Result<std::collections::HashMap<EntityId, Vec<EntityId>>, RepositoryError> {
419        self.table.get_relationship_many(ids, field)
420    }
421    pub fn get_relationship_count(
422        &self,
423        id: &EntityId,
424        field: &FeatureRelationshipField,
425    ) -> Result<usize, RepositoryError> {
426        self.table.get_relationship_count(id, field)
427    }
428    pub fn get_relationship_in_range(
429        &self,
430        id: &EntityId,
431        field: &FeatureRelationshipField,
432        offset: usize,
433        limit: usize,
434    ) -> Result<Vec<EntityId>, RepositoryError> {
435        self.table
436            .get_relationship_in_range(id, field, offset, limit)
437    }
438    pub fn get_relationships_from_right_ids(
439        &self,
440        field: &FeatureRelationshipField,
441        right_ids: &[EntityId],
442    ) -> Result<Vec<(EntityId, Vec<EntityId>)>, RepositoryError> {
443        self.table
444            .get_relationships_from_right_ids(field, right_ids)
445    }
446
447    pub fn set_relationship_multi(
448        &mut self,
449        event_buffer: &mut EventBuffer,
450        field: &FeatureRelationshipField,
451        relationships: Vec<(EntityId, Vec<EntityId>)>,
452    ) -> Result<(), RepositoryError> {
453        // Validate that all right_ids exist
454        let all_right_ids: Vec<EntityId> = relationships
455            .iter()
456            .flat_map(|(_, ids)| ids.iter().copied())
457            .collect();
458        if !all_right_ids.is_empty() {
459            match field {
460                FeatureRelationshipField::UseCases => {
461                    let child_repo =
462                        repository_factory::write::create_use_case_repository(self.transaction)?;
463                    let found = child_repo.get_multi(&all_right_ids)?;
464                    let missing: Vec<_> = all_right_ids
465                        .iter()
466                        .zip(found.iter())
467                        .filter(|(_, entity)| entity.is_none())
468                        .map(|(id, _)| *id)
469                        .collect();
470                    if !missing.is_empty() {
471                        return Err(RepositoryError::MissingRelationshipTarget {
472                            operation: "set_relationship_multi",
473                            ids: missing,
474                        });
475                    }
476                }
477            }
478        }
479        self.table
480            .set_relationship_multi(field, relationships.clone())?;
481        for (left_id, right_ids) in relationships {
482            event_buffer.push(Event {
483                origin: Origin::DirectAccess(DirectAccessEntity::Feature(EntityEvent::Updated)),
484                ids: vec![left_id],
485                data: Some(format!(
486                    "{}:{}",
487                    field,
488                    right_ids
489                        .iter()
490                        .map(|id| id.to_string())
491                        .collect::<Vec<_>>()
492                        .join(",")
493                )),
494            });
495        }
496        Ok(())
497    }
498
499    pub fn set_relationship(
500        &mut self,
501        event_buffer: &mut EventBuffer,
502        id: &EntityId,
503        field: &FeatureRelationshipField,
504        right_ids: &[EntityId],
505    ) -> Result<(), RepositoryError> {
506        // Validate that all right_ids exist
507        if !right_ids.is_empty() {
508            match field {
509                FeatureRelationshipField::UseCases => {
510                    let child_repo =
511                        repository_factory::write::create_use_case_repository(self.transaction)?;
512                    let found = child_repo.get_multi(right_ids)?;
513                    let missing: Vec<_> = right_ids
514                        .iter()
515                        .zip(found.iter())
516                        .filter(|(_, entity)| entity.is_none())
517                        .map(|(id, _)| *id)
518                        .collect();
519                    if !missing.is_empty() {
520                        return Err(RepositoryError::MissingRelationshipTarget {
521                            operation: "set_relationship",
522                            ids: missing,
523                        });
524                    }
525                }
526            }
527        }
528        self.table.set_relationship(id, field, right_ids)?;
529        event_buffer.push(Event {
530            origin: Origin::DirectAccess(DirectAccessEntity::Feature(EntityEvent::Updated)),
531            ids: vec![*id],
532            data: Some(format!(
533                "{}:{}",
534                field,
535                right_ids
536                    .iter()
537                    .map(|id| id.to_string())
538                    .collect::<Vec<_>>()
539                    .join(",")
540            )),
541        });
542        Ok(())
543    }
544
545    pub fn move_relationship_ids(
546        &mut self,
547        event_buffer: &mut EventBuffer,
548        id: &EntityId,
549        field: &FeatureRelationshipField,
550        ids_to_move: &[EntityId],
551        new_index: i32,
552    ) -> Result<Vec<EntityId>, RepositoryError> {
553        let reordered = self
554            .table
555            .move_relationship_ids(id, field, ids_to_move, new_index)?;
556        event_buffer.push(Event {
557            origin: Origin::DirectAccess(DirectAccessEntity::Feature(EntityEvent::Updated)),
558            ids: vec![*id],
559            data: Some(format!(
560                "{}:{}",
561                field,
562                reordered
563                    .iter()
564                    .map(|id| id.to_string())
565                    .collect::<Vec<_>>()
566                    .join(",")
567            )),
568        });
569        Ok(reordered)
570    }
571    pub fn get_relationships_from_owner(
572        &self,
573        owner_id: &EntityId,
574    ) -> Result<Vec<EntityId>, RepositoryError> {
575        let repo = repository_factory::write::create_workspace_repository(self.transaction)?;
576        repo.get_relationship(owner_id, &WorkspaceRelationshipField::Features)
577    }
578
579    pub fn set_relationships_in_owner(
580        &mut self,
581        event_buffer: &mut EventBuffer,
582        owner_id: &EntityId,
583        ids: &[EntityId],
584    ) -> Result<(), RepositoryError> {
585        let mut repo = repository_factory::write::create_workspace_repository(self.transaction)?;
586        repo.set_relationship(
587            event_buffer,
588            owner_id,
589            &WorkspaceRelationshipField::Features,
590            ids,
591        )
592    }
593
594    pub fn snapshot(&self, ids: &[EntityId]) -> Result<EntityTreeSnapshot, RepositoryError> {
595        // O(1) whole-store capture (im::HashMap structural sharing). Restore is scoped to `ids`.
596        let store_snap = self.transaction.snapshot_store();
597        Ok(EntityTreeSnapshot {
598            store_snapshot: Some(store_snap),
599            root_ids: ids.to_vec(),
600        })
601    }
602
603    pub fn restore(
604        &mut self,
605        event_buffer: &mut EventBuffer,
606        snap: &EntityTreeSnapshot,
607    ) -> Result<(), RepositoryError> {
608        let store_snap = snap
609            .store_snapshot
610            .as_ref()
611            .ok_or_else(|| RepositoryError::Serialization("missing store snapshot".into()))?;
612        let snap_store = store_snap
613            .downcast_ref::<HashMapStoreSnapshot>()
614            .ok_or_else(|| RepositoryError::Serialization("snapshot type mismatch".into()))?;
615
616        // Scoped restore: walk the subtree rooted at the captured ids (strong relationships only),
617        // in both the snapshot and the live store, and reconcile only it.
618        let mut visited = std::collections::HashSet::new();
619        self.restore_subtree(event_buffer, snap_store, &snap.root_ids, &mut visited)?;
620
621        // Surgically reconcile the root ids' placement in their (external) strong owner,
622        // preserving sibling edits made on other undo stacks.
623        self.reconcile_backref_workspace_features(event_buffer, snap_store, &snap.root_ids)?;
624
625        Ok(())
626    }
627
628    /// Restore the subtree rooted at `ids` (of this entity type) to the snapshot state, recursing
629    /// into strong children. Touches only the subtree: in-scope rows and the forward junctions they
630    /// own are restored wholesale; external referrers are reconciled surgically; everything else
631    /// (other trunks, non-undoable data) is left untouched. `visited` guards against cycles.
632    pub(crate) fn restore_subtree(
633        &self,
634        event_buffer: &mut EventBuffer,
635        snap: &HashMapStoreSnapshot,
636        ids: &[EntityId],
637        // Keyed by (entity type, id): EntityIds are per-type, not globally unique, so a bare
638        // id set would let one entity type's id falsely mask another's.
639        visited: &mut std::collections::HashSet<(&'static str, EntityId)>,
640    ) -> Result<(), RepositoryError> {
641        let ids: Vec<EntityId> = ids
642            .iter()
643            .copied()
644            .filter(|id| visited.insert(("feature", *id)))
645            .collect();
646        if ids.is_empty() {
647            return Ok(());
648        }
649
650        let store = self.transaction.get_store();
651
652        // Three-way partition by presence in snapshot vs live store (read before mutating).
653        let mut to_create: Vec<EntityId> = Vec::new(); // in snapshot, gone from live -> re-add
654        let mut to_update: Vec<EntityId> = Vec::new(); // in both                    -> revert
655        let mut to_delete: Vec<EntityId> = Vec::new(); // live only (created after)  -> delete
656        {
657            let live = read_or_recover(&store.features);
658            for id in &ids {
659                match (snap.features.contains_key(id), live.contains_key(id)) {
660                    (true, false) => to_create.push(*id),
661                    (true, true) => to_update.push(*id),
662                    (false, true) => to_delete.push(*id),
663                    (false, false) => {}
664                }
665            }
666        }
667
668        // 1. Recurse into strong children first (collect child ids from snapshot + live, then drop
669        //    all locks before recursing so self-referential ownership cannot deadlock).
670        {
671            let mut child_ids: Vec<EntityId> = Vec::new();
672            for id in to_create.iter().chain(to_update.iter()) {
673                if let Some(list) = snap.jn_use_case_from_feature_use_cases.get(id) {
674                    child_ids.extend(list.iter().copied());
675                }
676            }
677            {
678                let live_jn = read_or_recover(&store.jn_use_case_from_feature_use_cases);
679                for id in &ids {
680                    if let Some(list) = live_jn.get(id) {
681                        child_ids.extend(list.iter().copied());
682                    }
683                }
684            }
685            child_ids.sort();
686            child_ids.dedup();
687            if !child_ids.is_empty() {
688                repository_factory::write::create_use_case_repository(self.transaction)?
689                    .restore_subtree(event_buffer, snap, &child_ids, visited)?;
690            }
691        }
692
693        // 2. Entity rows: revert/re-add from snapshot, delete the ones created after it.
694        {
695            let mut live = write_or_recover(&store.features);
696            for id in to_create.iter().chain(to_update.iter()) {
697                if let Some(row) = snap.features.get(id) {
698                    live.insert(*id, row.clone());
699                }
700            }
701            for id in &to_delete {
702                live.remove(id);
703            }
704        }
705
706        // 3. This entity's own forward junctions (strong + weak): restored wholesale because the
707        //    junction key is in-scope (owned exclusively by this trunk).
708        {
709            let mut live_jn = write_or_recover(&store.jn_use_case_from_feature_use_cases);
710            for id in to_create.iter().chain(to_update.iter()) {
711                match snap.jn_use_case_from_feature_use_cases.get(id) {
712                    Some(v) => {
713                        live_jn.insert(*id, v.clone());
714                    }
715                    None => {
716                        live_jn.remove(id);
717                    }
718                }
719            }
720            for id in &to_delete {
721                live_jn.remove(id);
722            }
723        }
724
725        // 4. External weak referrers of these ids: reconcile surgically (membership only).
726        self.reconcile_backref_file_feature(event_buffer, snap, &ids)?;
727
728        // 5. Events: precise per-id (Created/Updated/Removed), not a whole-store storm.
729        if !to_create.is_empty() {
730            event_buffer.push(Event {
731                origin: Origin::DirectAccess(DirectAccessEntity::Feature(EntityEvent::Created)),
732                ids: to_create.clone(),
733                data: None,
734            });
735            event_buffer.push(Event {
736                origin: Origin::DirectAccess(DirectAccessEntity::Feature(EntityEvent::Updated)),
737                ids: to_create.clone(),
738                data: None,
739            });
740        }
741        if !to_update.is_empty() {
742            event_buffer.push(Event {
743                origin: Origin::DirectAccess(DirectAccessEntity::Feature(EntityEvent::Updated)),
744                ids: to_update.clone(),
745                data: None,
746            });
747        }
748        if !to_delete.is_empty() {
749            event_buffer.push(Event {
750                origin: Origin::DirectAccess(DirectAccessEntity::Feature(EntityEvent::Removed)),
751                ids: to_delete.clone(),
752                data: None,
753            });
754        }
755
756        Ok(())
757    }
758
759    /// Surgically reconcile one external referrer junction (out-of-scope key -> in-scope values):
760    /// `file.feature`.
761    fn reconcile_backref_file_feature(
762        &self,
763        event_buffer: &mut EventBuffer,
764        snap: &HashMapStoreSnapshot,
765        scope_ids: &[EntityId],
766    ) -> Result<(), RepositoryError> {
767        let scope: std::collections::HashSet<EntityId> = scope_ids.iter().copied().collect();
768        if scope.is_empty() {
769            return Ok(());
770        }
771        let store = self.transaction.get_store();
772
773        // External left keys whose ordered list references any scope id, in snapshot or live.
774        let mut left_keys: std::collections::HashSet<EntityId> = std::collections::HashSet::new();
775        for (left, rights) in snap.jn_feature_from_file_feature.iter() {
776            if rights.iter().any(|rid| scope.contains(rid)) {
777                left_keys.insert(*left);
778            }
779        }
780        {
781            let live_jn = read_or_recover(&store.jn_feature_from_file_feature);
782            for (left, rights) in live_jn.iter() {
783                if rights.iter().any(|rid| scope.contains(rid)) {
784                    left_keys.insert(*left);
785                }
786            }
787        }
788
789        for left in left_keys {
790            let snap_list: Vec<EntityId> = snap
791                .jn_feature_from_file_feature
792                .get(&left)
793                .cloned()
794                .unwrap_or_default();
795            let new_list = {
796                let live_jn = read_or_recover(&store.jn_feature_from_file_feature);
797                let live_list: Vec<EntityId> = live_jn.get(&left).cloned().unwrap_or_default();
798                let reconciled = crate::database::hashmap_store::reconcile_backref_list(
799                    &live_list, &snap_list, &scope,
800                );
801                if reconciled == live_list {
802                    None
803                } else {
804                    Some(reconciled)
805                }
806            };
807            if let Some(reconciled) = new_list {
808                write_or_recover(&store.jn_feature_from_file_feature).insert(left, reconciled);
809                event_buffer.push(Event {
810                    origin: Origin::DirectAccess(DirectAccessEntity::File(EntityEvent::Updated)),
811                    ids: vec![left],
812                    data: None,
813                });
814            }
815        }
816        Ok(())
817    }
818
819    /// Surgically reconcile one external referrer junction (out-of-scope key -> in-scope values):
820    /// `workspace.features`.
821    fn reconcile_backref_workspace_features(
822        &self,
823        event_buffer: &mut EventBuffer,
824        snap: &HashMapStoreSnapshot,
825        scope_ids: &[EntityId],
826    ) -> Result<(), RepositoryError> {
827        let scope: std::collections::HashSet<EntityId> = scope_ids.iter().copied().collect();
828        if scope.is_empty() {
829            return Ok(());
830        }
831        let store = self.transaction.get_store();
832
833        // External left keys whose ordered list references any scope id, in snapshot or live.
834        let mut left_keys: std::collections::HashSet<EntityId> = std::collections::HashSet::new();
835        for (left, rights) in snap.jn_feature_from_workspace_features.iter() {
836            if rights.iter().any(|rid| scope.contains(rid)) {
837                left_keys.insert(*left);
838            }
839        }
840        {
841            let live_jn = read_or_recover(&store.jn_feature_from_workspace_features);
842            for (left, rights) in live_jn.iter() {
843                if rights.iter().any(|rid| scope.contains(rid)) {
844                    left_keys.insert(*left);
845                }
846            }
847        }
848
849        for left in left_keys {
850            let snap_list: Vec<EntityId> = snap
851                .jn_feature_from_workspace_features
852                .get(&left)
853                .cloned()
854                .unwrap_or_default();
855            let new_list = {
856                let live_jn = read_or_recover(&store.jn_feature_from_workspace_features);
857                let live_list: Vec<EntityId> = live_jn.get(&left).cloned().unwrap_or_default();
858                let reconciled = crate::database::hashmap_store::reconcile_backref_list(
859                    &live_list, &snap_list, &scope,
860                );
861                if reconciled == live_list {
862                    None
863                } else {
864                    Some(reconciled)
865                }
866            };
867            if let Some(reconciled) = new_list {
868                write_or_recover(&store.jn_feature_from_workspace_features)
869                    .insert(left, reconciled);
870                event_buffer.push(Event {
871                    origin: Origin::DirectAccess(DirectAccessEntity::Workspace(
872                        EntityEvent::Updated,
873                    )),
874                    ids: vec![left],
875                    data: None,
876                });
877            }
878        }
879        Ok(())
880    }
881}
882
883pub struct FeatureRepositoryRO<'a> {
884    table: Box<dyn FeatureTableRO + 'a>,
885}
886impl<'a> FeatureRepositoryRO<'a> {
887    pub fn new(table: Box<dyn FeatureTableRO + 'a>) -> Self {
888        FeatureRepositoryRO { table }
889    }
890    pub fn get(&self, id: &EntityId) -> Result<Option<Feature>, RepositoryError> {
891        self.table.get(id)
892    }
893    pub fn get_multi(&self, ids: &[EntityId]) -> Result<Vec<Option<Feature>>, RepositoryError> {
894        self.table.get_multi(ids)
895    }
896    pub fn get_all(&self) -> Result<Vec<Feature>, RepositoryError> {
897        self.table.get_all()
898    }
899    pub fn get_relationship(
900        &self,
901        id: &EntityId,
902        field: &FeatureRelationshipField,
903    ) -> Result<Vec<EntityId>, RepositoryError> {
904        self.table.get_relationship(id, field)
905    }
906    pub fn get_relationship_many(
907        &self,
908        ids: &[EntityId],
909        field: &FeatureRelationshipField,
910    ) -> Result<std::collections::HashMap<EntityId, Vec<EntityId>>, RepositoryError> {
911        self.table.get_relationship_many(ids, field)
912    }
913    pub fn get_relationship_count(
914        &self,
915        id: &EntityId,
916        field: &FeatureRelationshipField,
917    ) -> Result<usize, RepositoryError> {
918        self.table.get_relationship_count(id, field)
919    }
920    pub fn get_relationship_in_range(
921        &self,
922        id: &EntityId,
923        field: &FeatureRelationshipField,
924        offset: usize,
925        limit: usize,
926    ) -> Result<Vec<EntityId>, RepositoryError> {
927        self.table
928            .get_relationship_in_range(id, field, offset, limit)
929    }
930    pub fn get_relationships_from_right_ids(
931        &self,
932        field: &FeatureRelationshipField,
933        right_ids: &[EntityId],
934    ) -> Result<Vec<(EntityId, Vec<EntityId>)>, RepositoryError> {
935        self.table
936            .get_relationships_from_right_ids(field, right_ids)
937    }
938}