Skip to main content

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