1use crate::entities::*;
11use crate::snapshot::{StoreSnapshot, StoreSnapshotTrait};
12use crate::types::EntityId;
13use im::HashMap;
14use std::sync::RwLock;
15
16#[derive(Debug)]
21pub struct HashMapStore {
22 pub roots: RwLock<HashMap<EntityId, Root>>,
24 pub workspaces: RwLock<HashMap<EntityId, Workspace>>,
25 pub systems: RwLock<HashMap<EntityId, System>>,
26 pub entities: RwLock<HashMap<EntityId, Entity>>,
27 pub fields: RwLock<HashMap<EntityId, Field>>,
28 pub features: RwLock<HashMap<EntityId, Feature>>,
29 pub files: RwLock<HashMap<EntityId, File>>,
30 pub use_cases: RwLock<HashMap<EntityId, UseCase>>,
31 pub dtos: RwLock<HashMap<EntityId, Dto>>,
32 pub dto_fields: RwLock<HashMap<EntityId, DtoField>>,
33 pub globals: RwLock<HashMap<EntityId, Global>>,
34 pub relationships: RwLock<HashMap<EntityId, Relationship>>,
35 pub user_interfaces: RwLock<HashMap<EntityId, UserInterface>>,
36
37 pub jn_system_from_root_system: RwLock<HashMapJunction>,
39 pub jn_workspace_from_root_workspace: RwLock<HashMapJunction>,
40 pub jn_entity_from_workspace_entities: RwLock<HashMapJunction>,
41 pub jn_feature_from_workspace_features: RwLock<HashMapJunction>,
42 pub jn_global_from_workspace_global: RwLock<HashMapJunction>,
43 pub jn_user_interface_from_workspace_user_interface: RwLock<HashMapJunction>,
44 pub jn_file_from_system_files: RwLock<HashMapJunction>,
45 pub jn_field_from_entity_fields: RwLock<HashMapJunction>,
46 pub jn_entity_from_entity_inherits_from: RwLock<HashMapJunction>,
47 pub jn_relationship_from_entity_relationships: RwLock<HashMapJunction>,
48 pub jn_entity_from_field_entity: RwLock<HashMapJunction>,
49 pub jn_use_case_from_feature_use_cases: RwLock<HashMapJunction>,
50 pub jn_entity_from_file_entity: RwLock<HashMapJunction>,
51 pub jn_feature_from_file_feature: RwLock<HashMapJunction>,
52 pub jn_field_from_file_field: RwLock<HashMapJunction>,
53 pub jn_use_case_from_file_use_case: RwLock<HashMapJunction>,
54 pub jn_dto_from_use_case_dto_in: RwLock<HashMapJunction>,
55 pub jn_dto_from_use_case_dto_out: RwLock<HashMapJunction>,
56 pub jn_entity_from_use_case_entities: RwLock<HashMapJunction>,
57 pub jn_dto_field_from_dto_fields: RwLock<HashMapJunction>,
58 pub jn_entity_from_relationship_left_entity: RwLock<HashMapJunction>,
59 pub jn_entity_from_relationship_right_entity: RwLock<HashMapJunction>,
60
61 pub counters: RwLock<std::collections::HashMap<String, EntityId>>,
63
64 savepoints: RwLock<std::collections::HashMap<u64, HashMapStoreSnapshot>>,
66 next_savepoint_id: RwLock<u64>,
67}
68impl Default for HashMapStore {
69 fn default() -> Self {
70 Self {
71 roots: RwLock::default(),
72 workspaces: RwLock::default(),
73 systems: RwLock::default(),
74 entities: RwLock::default(),
75 fields: RwLock::default(),
76 features: RwLock::default(),
77 files: RwLock::default(),
78 use_cases: RwLock::default(),
79 dtos: RwLock::default(),
80 dto_fields: RwLock::default(),
81 globals: RwLock::default(),
82 relationships: RwLock::default(),
83 user_interfaces: RwLock::default(),
84 jn_system_from_root_system: RwLock::new(HashMapJunction::exclusive()),
85 jn_workspace_from_root_workspace: RwLock::new(HashMapJunction::exclusive()),
86 jn_entity_from_workspace_entities: RwLock::new(HashMapJunction::exclusive()),
87 jn_feature_from_workspace_features: RwLock::new(HashMapJunction::exclusive()),
88 jn_global_from_workspace_global: RwLock::new(HashMapJunction::exclusive()),
89 jn_user_interface_from_workspace_user_interface: RwLock::new(
90 HashMapJunction::exclusive(),
91 ),
92 jn_file_from_system_files: RwLock::new(HashMapJunction::exclusive()),
93 jn_field_from_entity_fields: RwLock::new(HashMapJunction::exclusive()),
94 jn_entity_from_entity_inherits_from: RwLock::new(HashMapJunction::shared()),
95 jn_relationship_from_entity_relationships: RwLock::new(HashMapJunction::exclusive()),
96 jn_entity_from_field_entity: RwLock::new(HashMapJunction::shared()),
97 jn_use_case_from_feature_use_cases: RwLock::new(HashMapJunction::exclusive()),
98 jn_entity_from_file_entity: RwLock::new(HashMapJunction::shared()),
99 jn_feature_from_file_feature: RwLock::new(HashMapJunction::shared()),
100 jn_field_from_file_field: RwLock::new(HashMapJunction::shared()),
101 jn_use_case_from_file_use_case: RwLock::new(HashMapJunction::shared()),
102 jn_dto_from_use_case_dto_in: RwLock::new(HashMapJunction::exclusive()),
103 jn_dto_from_use_case_dto_out: RwLock::new(HashMapJunction::exclusive()),
104 jn_entity_from_use_case_entities: RwLock::new(HashMapJunction::shared()),
105 jn_dto_field_from_dto_fields: RwLock::new(HashMapJunction::exclusive()),
106 jn_entity_from_relationship_left_entity: RwLock::new(HashMapJunction::shared()),
107 jn_entity_from_relationship_right_entity: RwLock::new(HashMapJunction::shared()),
108 counters: RwLock::default(),
109 savepoints: RwLock::default(),
110 next_savepoint_id: RwLock::default(),
111 }
112 }
113}
114
115impl HashMapStore {
116 pub fn new() -> Self {
117 Self::default()
118 }
119
120 pub fn snapshot(&self) -> HashMapStoreSnapshot {
125 HashMapStoreSnapshot {
126 roots: read_or_recover(&self.roots).clone(),
127 workspaces: read_or_recover(&self.workspaces).clone(),
128 systems: read_or_recover(&self.systems).clone(),
129 entities: read_or_recover(&self.entities).clone(),
130 fields: read_or_recover(&self.fields).clone(),
131 features: read_or_recover(&self.features).clone(),
132 files: read_or_recover(&self.files).clone(),
133 use_cases: read_or_recover(&self.use_cases).clone(),
134 dtos: read_or_recover(&self.dtos).clone(),
135 dto_fields: read_or_recover(&self.dto_fields).clone(),
136 globals: read_or_recover(&self.globals).clone(),
137 relationships: read_or_recover(&self.relationships).clone(),
138 user_interfaces: read_or_recover(&self.user_interfaces).clone(),
139 jn_system_from_root_system: read_or_recover(&self.jn_system_from_root_system).clone(),
140 jn_workspace_from_root_workspace: read_or_recover(
141 &self.jn_workspace_from_root_workspace,
142 )
143 .clone(),
144 jn_entity_from_workspace_entities: read_or_recover(
145 &self.jn_entity_from_workspace_entities,
146 )
147 .clone(),
148 jn_feature_from_workspace_features: read_or_recover(
149 &self.jn_feature_from_workspace_features,
150 )
151 .clone(),
152 jn_global_from_workspace_global: read_or_recover(&self.jn_global_from_workspace_global)
153 .clone(),
154 jn_user_interface_from_workspace_user_interface: read_or_recover(
155 &self.jn_user_interface_from_workspace_user_interface,
156 )
157 .clone(),
158 jn_file_from_system_files: read_or_recover(&self.jn_file_from_system_files).clone(),
159 jn_field_from_entity_fields: read_or_recover(&self.jn_field_from_entity_fields).clone(),
160 jn_entity_from_entity_inherits_from: read_or_recover(
161 &self.jn_entity_from_entity_inherits_from,
162 )
163 .clone(),
164 jn_relationship_from_entity_relationships: read_or_recover(
165 &self.jn_relationship_from_entity_relationships,
166 )
167 .clone(),
168 jn_entity_from_field_entity: read_or_recover(&self.jn_entity_from_field_entity).clone(),
169 jn_use_case_from_feature_use_cases: read_or_recover(
170 &self.jn_use_case_from_feature_use_cases,
171 )
172 .clone(),
173 jn_entity_from_file_entity: read_or_recover(&self.jn_entity_from_file_entity).clone(),
174 jn_feature_from_file_feature: read_or_recover(&self.jn_feature_from_file_feature)
175 .clone(),
176 jn_field_from_file_field: read_or_recover(&self.jn_field_from_file_field).clone(),
177 jn_use_case_from_file_use_case: read_or_recover(&self.jn_use_case_from_file_use_case)
178 .clone(),
179 jn_dto_from_use_case_dto_in: read_or_recover(&self.jn_dto_from_use_case_dto_in).clone(),
180 jn_dto_from_use_case_dto_out: read_or_recover(&self.jn_dto_from_use_case_dto_out)
181 .clone(),
182 jn_entity_from_use_case_entities: read_or_recover(
183 &self.jn_entity_from_use_case_entities,
184 )
185 .clone(),
186 jn_dto_field_from_dto_fields: read_or_recover(&self.jn_dto_field_from_dto_fields)
187 .clone(),
188 jn_entity_from_relationship_left_entity: read_or_recover(
189 &self.jn_entity_from_relationship_left_entity,
190 )
191 .clone(),
192 jn_entity_from_relationship_right_entity: read_or_recover(
193 &self.jn_entity_from_relationship_right_entity,
194 )
195 .clone(),
196 counters: read_or_recover(&self.counters).clone(),
197 }
198 }
199
200 pub fn freeze(&self) -> HashMapStore {
227 let g_roots = read_or_recover(&self.roots);
230 let g_workspaces = read_or_recover(&self.workspaces);
231 let g_systems = read_or_recover(&self.systems);
232 let g_entities = read_or_recover(&self.entities);
233 let g_fields = read_or_recover(&self.fields);
234 let g_features = read_or_recover(&self.features);
235 let g_files = read_or_recover(&self.files);
236 let g_use_cases = read_or_recover(&self.use_cases);
237 let g_dtos = read_or_recover(&self.dtos);
238 let g_dto_fields = read_or_recover(&self.dto_fields);
239 let g_globals = read_or_recover(&self.globals);
240 let g_relationships = read_or_recover(&self.relationships);
241 let g_user_interfaces = read_or_recover(&self.user_interfaces);
242 let g_jn_system_from_root_system = read_or_recover(&self.jn_system_from_root_system);
243 let g_jn_workspace_from_root_workspace =
244 read_or_recover(&self.jn_workspace_from_root_workspace);
245 let g_jn_entity_from_workspace_entities =
246 read_or_recover(&self.jn_entity_from_workspace_entities);
247 let g_jn_feature_from_workspace_features =
248 read_or_recover(&self.jn_feature_from_workspace_features);
249 let g_jn_global_from_workspace_global =
250 read_or_recover(&self.jn_global_from_workspace_global);
251 let g_jn_user_interface_from_workspace_user_interface =
252 read_or_recover(&self.jn_user_interface_from_workspace_user_interface);
253 let g_jn_file_from_system_files = read_or_recover(&self.jn_file_from_system_files);
254 let g_jn_field_from_entity_fields = read_or_recover(&self.jn_field_from_entity_fields);
255 let g_jn_entity_from_entity_inherits_from =
256 read_or_recover(&self.jn_entity_from_entity_inherits_from);
257 let g_jn_relationship_from_entity_relationships =
258 read_or_recover(&self.jn_relationship_from_entity_relationships);
259 let g_jn_entity_from_field_entity = read_or_recover(&self.jn_entity_from_field_entity);
260 let g_jn_use_case_from_feature_use_cases =
261 read_or_recover(&self.jn_use_case_from_feature_use_cases);
262 let g_jn_entity_from_file_entity = read_or_recover(&self.jn_entity_from_file_entity);
263 let g_jn_feature_from_file_feature = read_or_recover(&self.jn_feature_from_file_feature);
264 let g_jn_field_from_file_field = read_or_recover(&self.jn_field_from_file_field);
265 let g_jn_use_case_from_file_use_case =
266 read_or_recover(&self.jn_use_case_from_file_use_case);
267 let g_jn_dto_from_use_case_dto_in = read_or_recover(&self.jn_dto_from_use_case_dto_in);
268 let g_jn_dto_from_use_case_dto_out = read_or_recover(&self.jn_dto_from_use_case_dto_out);
269 let g_jn_entity_from_use_case_entities =
270 read_or_recover(&self.jn_entity_from_use_case_entities);
271 let g_jn_dto_field_from_dto_fields = read_or_recover(&self.jn_dto_field_from_dto_fields);
272 let g_jn_entity_from_relationship_left_entity =
273 read_or_recover(&self.jn_entity_from_relationship_left_entity);
274 let g_jn_entity_from_relationship_right_entity =
275 read_or_recover(&self.jn_entity_from_relationship_right_entity);
276 let g_counters = read_or_recover(&self.counters);
277
278 HashMapStore {
279 roots: RwLock::new(g_roots.clone()),
280 workspaces: RwLock::new(g_workspaces.clone()),
281 systems: RwLock::new(g_systems.clone()),
282 entities: RwLock::new(g_entities.clone()),
283 fields: RwLock::new(g_fields.clone()),
284 features: RwLock::new(g_features.clone()),
285 files: RwLock::new(g_files.clone()),
286 use_cases: RwLock::new(g_use_cases.clone()),
287 dtos: RwLock::new(g_dtos.clone()),
288 dto_fields: RwLock::new(g_dto_fields.clone()),
289 globals: RwLock::new(g_globals.clone()),
290 relationships: RwLock::new(g_relationships.clone()),
291 user_interfaces: RwLock::new(g_user_interfaces.clone()),
292 jn_system_from_root_system: RwLock::new(g_jn_system_from_root_system.clone()),
293 jn_workspace_from_root_workspace: RwLock::new(
294 g_jn_workspace_from_root_workspace.clone(),
295 ),
296 jn_entity_from_workspace_entities: RwLock::new(
297 g_jn_entity_from_workspace_entities.clone(),
298 ),
299 jn_feature_from_workspace_features: RwLock::new(
300 g_jn_feature_from_workspace_features.clone(),
301 ),
302 jn_global_from_workspace_global: RwLock::new(g_jn_global_from_workspace_global.clone()),
303 jn_user_interface_from_workspace_user_interface: RwLock::new(
304 g_jn_user_interface_from_workspace_user_interface.clone(),
305 ),
306 jn_file_from_system_files: RwLock::new(g_jn_file_from_system_files.clone()),
307 jn_field_from_entity_fields: RwLock::new(g_jn_field_from_entity_fields.clone()),
308 jn_entity_from_entity_inherits_from: RwLock::new(
309 g_jn_entity_from_entity_inherits_from.clone(),
310 ),
311 jn_relationship_from_entity_relationships: RwLock::new(
312 g_jn_relationship_from_entity_relationships.clone(),
313 ),
314 jn_entity_from_field_entity: RwLock::new(g_jn_entity_from_field_entity.clone()),
315 jn_use_case_from_feature_use_cases: RwLock::new(
316 g_jn_use_case_from_feature_use_cases.clone(),
317 ),
318 jn_entity_from_file_entity: RwLock::new(g_jn_entity_from_file_entity.clone()),
319 jn_feature_from_file_feature: RwLock::new(g_jn_feature_from_file_feature.clone()),
320 jn_field_from_file_field: RwLock::new(g_jn_field_from_file_field.clone()),
321 jn_use_case_from_file_use_case: RwLock::new(g_jn_use_case_from_file_use_case.clone()),
322 jn_dto_from_use_case_dto_in: RwLock::new(g_jn_dto_from_use_case_dto_in.clone()),
323 jn_dto_from_use_case_dto_out: RwLock::new(g_jn_dto_from_use_case_dto_out.clone()),
324 jn_entity_from_use_case_entities: RwLock::new(
325 g_jn_entity_from_use_case_entities.clone(),
326 ),
327 jn_dto_field_from_dto_fields: RwLock::new(g_jn_dto_field_from_dto_fields.clone()),
328 jn_entity_from_relationship_left_entity: RwLock::new(
329 g_jn_entity_from_relationship_left_entity.clone(),
330 ),
331 jn_entity_from_relationship_right_entity: RwLock::new(
332 g_jn_entity_from_relationship_right_entity.clone(),
333 ),
334 counters: RwLock::new(g_counters.clone()),
335 savepoints: RwLock::new(std::collections::HashMap::new()),
336 next_savepoint_id: RwLock::new(0),
337 }
338 }
339
340 pub fn restore(&self, snap: &HashMapStoreSnapshot) {
342 *write_or_recover(&self.roots) = snap.roots.clone();
343 *write_or_recover(&self.workspaces) = snap.workspaces.clone();
344 *write_or_recover(&self.systems) = snap.systems.clone();
345 *write_or_recover(&self.entities) = snap.entities.clone();
346 *write_or_recover(&self.fields) = snap.fields.clone();
347 *write_or_recover(&self.features) = snap.features.clone();
348 *write_or_recover(&self.files) = snap.files.clone();
349 *write_or_recover(&self.use_cases) = snap.use_cases.clone();
350 *write_or_recover(&self.dtos) = snap.dtos.clone();
351 *write_or_recover(&self.dto_fields) = snap.dto_fields.clone();
352 *write_or_recover(&self.globals) = snap.globals.clone();
353 *write_or_recover(&self.relationships) = snap.relationships.clone();
354 *write_or_recover(&self.user_interfaces) = snap.user_interfaces.clone();
355 *write_or_recover(&self.jn_system_from_root_system) =
356 snap.jn_system_from_root_system.clone();
357 *write_or_recover(&self.jn_workspace_from_root_workspace) =
358 snap.jn_workspace_from_root_workspace.clone();
359 *write_or_recover(&self.jn_entity_from_workspace_entities) =
360 snap.jn_entity_from_workspace_entities.clone();
361 *write_or_recover(&self.jn_feature_from_workspace_features) =
362 snap.jn_feature_from_workspace_features.clone();
363 *write_or_recover(&self.jn_global_from_workspace_global) =
364 snap.jn_global_from_workspace_global.clone();
365 *write_or_recover(&self.jn_user_interface_from_workspace_user_interface) =
366 snap.jn_user_interface_from_workspace_user_interface.clone();
367 *write_or_recover(&self.jn_file_from_system_files) = snap.jn_file_from_system_files.clone();
368 *write_or_recover(&self.jn_field_from_entity_fields) =
369 snap.jn_field_from_entity_fields.clone();
370 *write_or_recover(&self.jn_entity_from_entity_inherits_from) =
371 snap.jn_entity_from_entity_inherits_from.clone();
372 *write_or_recover(&self.jn_relationship_from_entity_relationships) =
373 snap.jn_relationship_from_entity_relationships.clone();
374 *write_or_recover(&self.jn_entity_from_field_entity) =
375 snap.jn_entity_from_field_entity.clone();
376 *write_or_recover(&self.jn_use_case_from_feature_use_cases) =
377 snap.jn_use_case_from_feature_use_cases.clone();
378 *write_or_recover(&self.jn_entity_from_file_entity) =
379 snap.jn_entity_from_file_entity.clone();
380 *write_or_recover(&self.jn_feature_from_file_feature) =
381 snap.jn_feature_from_file_feature.clone();
382 *write_or_recover(&self.jn_field_from_file_field) = snap.jn_field_from_file_field.clone();
383 *write_or_recover(&self.jn_use_case_from_file_use_case) =
384 snap.jn_use_case_from_file_use_case.clone();
385 *write_or_recover(&self.jn_dto_from_use_case_dto_in) =
386 snap.jn_dto_from_use_case_dto_in.clone();
387 *write_or_recover(&self.jn_dto_from_use_case_dto_out) =
388 snap.jn_dto_from_use_case_dto_out.clone();
389 *write_or_recover(&self.jn_entity_from_use_case_entities) =
390 snap.jn_entity_from_use_case_entities.clone();
391 *write_or_recover(&self.jn_dto_field_from_dto_fields) =
392 snap.jn_dto_field_from_dto_fields.clone();
393 *write_or_recover(&self.jn_entity_from_relationship_left_entity) =
394 snap.jn_entity_from_relationship_left_entity.clone();
395 *write_or_recover(&self.jn_entity_from_relationship_right_entity) =
396 snap.jn_entity_from_relationship_right_entity.clone();
397 *write_or_recover(&self.counters) = snap.counters.clone();
398 }
399
400 pub fn create_savepoint(&self) -> u64 {
403 let snap = self.snapshot();
404 let mut id_counter = write_or_recover(&self.next_savepoint_id);
405 let id = *id_counter;
406 *id_counter += 1;
407 write_or_recover(&self.savepoints).insert(id, snap);
408 id
409 }
410
411 pub fn restore_savepoint(&self, savepoint_id: u64) {
413 let Some(snap) = read_or_recover(&self.savepoints)
423 .get(&savepoint_id)
424 .cloned()
425 else {
426 debug_assert!(
427 false,
428 "restore_savepoint: savepoint {savepoint_id} does not exist — it was already \
429 restored, discarded, or never created"
430 );
431 return;
432 };
433 self.restore(&snap);
434 }
435
436 pub fn discard_savepoint(&self, savepoint_id: u64) {
439 write_or_recover(&self.savepoints).remove(&savepoint_id);
440 }
441
442 pub(crate) fn next_id(&self, entity_name: &str) -> EntityId {
444 let mut counters = write_or_recover(&self.counters);
445 let counter = counters.entry(entity_name.to_string()).or_insert(1);
446 let id = *counter;
447 *counter += 1;
448 id
449 }
450
451 pub fn restore_without_counters(&self, snap: &HashMapStoreSnapshot) {
454 *write_or_recover(&self.roots) = snap.roots.clone();
455 *write_or_recover(&self.workspaces) = snap.workspaces.clone();
456 *write_or_recover(&self.systems) = snap.systems.clone();
457 *write_or_recover(&self.entities) = snap.entities.clone();
458 *write_or_recover(&self.fields) = snap.fields.clone();
459 *write_or_recover(&self.features) = snap.features.clone();
460 *write_or_recover(&self.files) = snap.files.clone();
461 *write_or_recover(&self.use_cases) = snap.use_cases.clone();
462 *write_or_recover(&self.dtos) = snap.dtos.clone();
463 *write_or_recover(&self.dto_fields) = snap.dto_fields.clone();
464 *write_or_recover(&self.globals) = snap.globals.clone();
465 *write_or_recover(&self.relationships) = snap.relationships.clone();
466 *write_or_recover(&self.user_interfaces) = snap.user_interfaces.clone();
467 *write_or_recover(&self.jn_system_from_root_system) =
468 snap.jn_system_from_root_system.clone();
469 *write_or_recover(&self.jn_workspace_from_root_workspace) =
470 snap.jn_workspace_from_root_workspace.clone();
471 *write_or_recover(&self.jn_entity_from_workspace_entities) =
472 snap.jn_entity_from_workspace_entities.clone();
473 *write_or_recover(&self.jn_feature_from_workspace_features) =
474 snap.jn_feature_from_workspace_features.clone();
475 *write_or_recover(&self.jn_global_from_workspace_global) =
476 snap.jn_global_from_workspace_global.clone();
477 *write_or_recover(&self.jn_user_interface_from_workspace_user_interface) =
478 snap.jn_user_interface_from_workspace_user_interface.clone();
479 *write_or_recover(&self.jn_file_from_system_files) = snap.jn_file_from_system_files.clone();
480 *write_or_recover(&self.jn_field_from_entity_fields) =
481 snap.jn_field_from_entity_fields.clone();
482 *write_or_recover(&self.jn_entity_from_entity_inherits_from) =
483 snap.jn_entity_from_entity_inherits_from.clone();
484 *write_or_recover(&self.jn_relationship_from_entity_relationships) =
485 snap.jn_relationship_from_entity_relationships.clone();
486 *write_or_recover(&self.jn_entity_from_field_entity) =
487 snap.jn_entity_from_field_entity.clone();
488 *write_or_recover(&self.jn_use_case_from_feature_use_cases) =
489 snap.jn_use_case_from_feature_use_cases.clone();
490 *write_or_recover(&self.jn_entity_from_file_entity) =
491 snap.jn_entity_from_file_entity.clone();
492 *write_or_recover(&self.jn_feature_from_file_feature) =
493 snap.jn_feature_from_file_feature.clone();
494 *write_or_recover(&self.jn_field_from_file_field) = snap.jn_field_from_file_field.clone();
495 *write_or_recover(&self.jn_use_case_from_file_use_case) =
496 snap.jn_use_case_from_file_use_case.clone();
497 *write_or_recover(&self.jn_dto_from_use_case_dto_in) =
498 snap.jn_dto_from_use_case_dto_in.clone();
499 *write_or_recover(&self.jn_dto_from_use_case_dto_out) =
500 snap.jn_dto_from_use_case_dto_out.clone();
501 *write_or_recover(&self.jn_entity_from_use_case_entities) =
502 snap.jn_entity_from_use_case_entities.clone();
503 *write_or_recover(&self.jn_dto_field_from_dto_fields) =
504 snap.jn_dto_field_from_dto_fields.clone();
505 *write_or_recover(&self.jn_entity_from_relationship_left_entity) =
506 snap.jn_entity_from_relationship_left_entity.clone();
507 *write_or_recover(&self.jn_entity_from_relationship_right_entity) =
508 snap.jn_entity_from_relationship_right_entity.clone();
509 }
511
512 pub fn store_snapshot(&self) -> StoreSnapshot {
514 StoreSnapshot::new(self.snapshot())
515 }
516
517 pub fn restore_store_snapshot(&self, snap: &StoreSnapshot) {
519 let s = snap
520 .downcast_ref::<HashMapStoreSnapshot>()
521 .expect("StoreSnapshot must contain HashMapStoreSnapshot");
522 self.restore_without_counters(s);
523 }
524
525 pub fn check_junction_indexes(&self) -> Result<(), String> {
531 {
532 let jn = read_or_recover(&self.jn_system_from_root_system);
533 if !jn.is_exclusive() {
534 return Err(
535 "jn_system_from_root_system: exclusivity does not match the relationship"
536 .into(),
537 );
538 }
539 jn.check_index()
540 .map_err(|e| format!("jn_system_from_root_system: {e}"))?;
541 }
542 {
543 let jn = read_or_recover(&self.jn_workspace_from_root_workspace);
544 if !jn.is_exclusive() {
545 return Err(
546 "jn_workspace_from_root_workspace: exclusivity does not match the relationship"
547 .into(),
548 );
549 }
550 jn.check_index()
551 .map_err(|e| format!("jn_workspace_from_root_workspace: {e}"))?;
552 }
553 {
554 let jn = read_or_recover(&self.jn_entity_from_workspace_entities);
555 if !jn.is_exclusive() {
556 return Err("jn_entity_from_workspace_entities: exclusivity does not match the relationship".into());
557 }
558 jn.check_index()
559 .map_err(|e| format!("jn_entity_from_workspace_entities: {e}"))?;
560 }
561 {
562 let jn = read_or_recover(&self.jn_feature_from_workspace_features);
563 if !jn.is_exclusive() {
564 return Err("jn_feature_from_workspace_features: exclusivity does not match the relationship".into());
565 }
566 jn.check_index()
567 .map_err(|e| format!("jn_feature_from_workspace_features: {e}"))?;
568 }
569 {
570 let jn = read_or_recover(&self.jn_global_from_workspace_global);
571 if !jn.is_exclusive() {
572 return Err(
573 "jn_global_from_workspace_global: exclusivity does not match the relationship"
574 .into(),
575 );
576 }
577 jn.check_index()
578 .map_err(|e| format!("jn_global_from_workspace_global: {e}"))?;
579 }
580 {
581 let jn = read_or_recover(&self.jn_user_interface_from_workspace_user_interface);
582 if !jn.is_exclusive() {
583 return Err("jn_user_interface_from_workspace_user_interface: exclusivity does not match the relationship".into());
584 }
585 jn.check_index()
586 .map_err(|e| format!("jn_user_interface_from_workspace_user_interface: {e}"))?;
587 }
588 {
589 let jn = read_or_recover(&self.jn_file_from_system_files);
590 if !jn.is_exclusive() {
591 return Err(
592 "jn_file_from_system_files: exclusivity does not match the relationship".into(),
593 );
594 }
595 jn.check_index()
596 .map_err(|e| format!("jn_file_from_system_files: {e}"))?;
597 }
598 {
599 let jn = read_or_recover(&self.jn_field_from_entity_fields);
600 if !jn.is_exclusive() {
601 return Err(
602 "jn_field_from_entity_fields: exclusivity does not match the relationship"
603 .into(),
604 );
605 }
606 jn.check_index()
607 .map_err(|e| format!("jn_field_from_entity_fields: {e}"))?;
608 }
609 {
610 let jn = read_or_recover(&self.jn_entity_from_entity_inherits_from);
611 if jn.is_exclusive() {
612 return Err("jn_entity_from_entity_inherits_from: exclusivity does not match the relationship".into());
613 }
614 jn.check_index()
615 .map_err(|e| format!("jn_entity_from_entity_inherits_from: {e}"))?;
616 }
617 {
618 let jn = read_or_recover(&self.jn_relationship_from_entity_relationships);
619 if !jn.is_exclusive() {
620 return Err("jn_relationship_from_entity_relationships: exclusivity does not match the relationship".into());
621 }
622 jn.check_index()
623 .map_err(|e| format!("jn_relationship_from_entity_relationships: {e}"))?;
624 }
625 {
626 let jn = read_or_recover(&self.jn_entity_from_field_entity);
627 if jn.is_exclusive() {
628 return Err(
629 "jn_entity_from_field_entity: exclusivity does not match the relationship"
630 .into(),
631 );
632 }
633 jn.check_index()
634 .map_err(|e| format!("jn_entity_from_field_entity: {e}"))?;
635 }
636 {
637 let jn = read_or_recover(&self.jn_use_case_from_feature_use_cases);
638 if !jn.is_exclusive() {
639 return Err("jn_use_case_from_feature_use_cases: exclusivity does not match the relationship".into());
640 }
641 jn.check_index()
642 .map_err(|e| format!("jn_use_case_from_feature_use_cases: {e}"))?;
643 }
644 {
645 let jn = read_or_recover(&self.jn_entity_from_file_entity);
646 if jn.is_exclusive() {
647 return Err(
648 "jn_entity_from_file_entity: exclusivity does not match the relationship"
649 .into(),
650 );
651 }
652 jn.check_index()
653 .map_err(|e| format!("jn_entity_from_file_entity: {e}"))?;
654 }
655 {
656 let jn = read_or_recover(&self.jn_feature_from_file_feature);
657 if jn.is_exclusive() {
658 return Err(
659 "jn_feature_from_file_feature: exclusivity does not match the relationship"
660 .into(),
661 );
662 }
663 jn.check_index()
664 .map_err(|e| format!("jn_feature_from_file_feature: {e}"))?;
665 }
666 {
667 let jn = read_or_recover(&self.jn_field_from_file_field);
668 if jn.is_exclusive() {
669 return Err(
670 "jn_field_from_file_field: exclusivity does not match the relationship".into(),
671 );
672 }
673 jn.check_index()
674 .map_err(|e| format!("jn_field_from_file_field: {e}"))?;
675 }
676 {
677 let jn = read_or_recover(&self.jn_use_case_from_file_use_case);
678 if jn.is_exclusive() {
679 return Err(
680 "jn_use_case_from_file_use_case: exclusivity does not match the relationship"
681 .into(),
682 );
683 }
684 jn.check_index()
685 .map_err(|e| format!("jn_use_case_from_file_use_case: {e}"))?;
686 }
687 {
688 let jn = read_or_recover(&self.jn_dto_from_use_case_dto_in);
689 if !jn.is_exclusive() {
690 return Err(
691 "jn_dto_from_use_case_dto_in: exclusivity does not match the relationship"
692 .into(),
693 );
694 }
695 jn.check_index()
696 .map_err(|e| format!("jn_dto_from_use_case_dto_in: {e}"))?;
697 }
698 {
699 let jn = read_or_recover(&self.jn_dto_from_use_case_dto_out);
700 if !jn.is_exclusive() {
701 return Err(
702 "jn_dto_from_use_case_dto_out: exclusivity does not match the relationship"
703 .into(),
704 );
705 }
706 jn.check_index()
707 .map_err(|e| format!("jn_dto_from_use_case_dto_out: {e}"))?;
708 }
709 {
710 let jn = read_or_recover(&self.jn_entity_from_use_case_entities);
711 if jn.is_exclusive() {
712 return Err(
713 "jn_entity_from_use_case_entities: exclusivity does not match the relationship"
714 .into(),
715 );
716 }
717 jn.check_index()
718 .map_err(|e| format!("jn_entity_from_use_case_entities: {e}"))?;
719 }
720 {
721 let jn = read_or_recover(&self.jn_dto_field_from_dto_fields);
722 if !jn.is_exclusive() {
723 return Err(
724 "jn_dto_field_from_dto_fields: exclusivity does not match the relationship"
725 .into(),
726 );
727 }
728 jn.check_index()
729 .map_err(|e| format!("jn_dto_field_from_dto_fields: {e}"))?;
730 }
731 {
732 let jn = read_or_recover(&self.jn_entity_from_relationship_left_entity);
733 if jn.is_exclusive() {
734 return Err("jn_entity_from_relationship_left_entity: exclusivity does not match the relationship".into());
735 }
736 jn.check_index()
737 .map_err(|e| format!("jn_entity_from_relationship_left_entity: {e}"))?;
738 }
739 {
740 let jn = read_or_recover(&self.jn_entity_from_relationship_right_entity);
741 if jn.is_exclusive() {
742 return Err("jn_entity_from_relationship_right_entity: exclusivity does not match the relationship".into());
743 }
744 jn.check_index()
745 .map_err(|e| format!("jn_entity_from_relationship_right_entity: {e}"))?;
746 }
747 Ok(())
748 }
749}
750
751#[derive(Debug, Clone)]
757pub struct HashMapStoreSnapshot {
758 pub(crate) roots: HashMap<EntityId, Root>,
759 pub(crate) workspaces: HashMap<EntityId, Workspace>,
760 pub(crate) systems: HashMap<EntityId, System>,
761 pub(crate) entities: HashMap<EntityId, Entity>,
762 pub(crate) fields: HashMap<EntityId, Field>,
763 pub(crate) features: HashMap<EntityId, Feature>,
764 pub(crate) files: HashMap<EntityId, File>,
765 pub(crate) use_cases: HashMap<EntityId, UseCase>,
766 pub(crate) dtos: HashMap<EntityId, Dto>,
767 pub(crate) dto_fields: HashMap<EntityId, DtoField>,
768 pub(crate) globals: HashMap<EntityId, Global>,
769 pub(crate) relationships: HashMap<EntityId, Relationship>,
770 pub(crate) user_interfaces: HashMap<EntityId, UserInterface>,
771 pub(crate) jn_system_from_root_system: HashMapJunction,
772 pub(crate) jn_workspace_from_root_workspace: HashMapJunction,
773 pub(crate) jn_entity_from_workspace_entities: HashMapJunction,
774 pub(crate) jn_feature_from_workspace_features: HashMapJunction,
775 pub(crate) jn_global_from_workspace_global: HashMapJunction,
776 pub(crate) jn_user_interface_from_workspace_user_interface: HashMapJunction,
777 pub(crate) jn_file_from_system_files: HashMapJunction,
778 pub(crate) jn_field_from_entity_fields: HashMapJunction,
779 pub(crate) jn_entity_from_entity_inherits_from: HashMapJunction,
780 pub(crate) jn_relationship_from_entity_relationships: HashMapJunction,
781 pub(crate) jn_entity_from_field_entity: HashMapJunction,
782 pub(crate) jn_use_case_from_feature_use_cases: HashMapJunction,
783 pub(crate) jn_entity_from_file_entity: HashMapJunction,
784 pub(crate) jn_feature_from_file_feature: HashMapJunction,
785 pub(crate) jn_field_from_file_field: HashMapJunction,
786 pub(crate) jn_use_case_from_file_use_case: HashMapJunction,
787 pub(crate) jn_dto_from_use_case_dto_in: HashMapJunction,
788 pub(crate) jn_dto_from_use_case_dto_out: HashMapJunction,
789 pub(crate) jn_entity_from_use_case_entities: HashMapJunction,
790 pub(crate) jn_dto_field_from_dto_fields: HashMapJunction,
791 pub(crate) jn_entity_from_relationship_left_entity: HashMapJunction,
792 pub(crate) jn_entity_from_relationship_right_entity: HashMapJunction,
793 counters: std::collections::HashMap<String, EntityId>,
794}
795
796impl StoreSnapshotTrait for HashMapStoreSnapshot {
797 fn clone_box(&self) -> Box<dyn StoreSnapshotTrait> {
798 Box::new(self.clone())
799 }
800
801 fn as_any(&self) -> &dyn std::any::Any {
802 self
803 }
804}
805
806#[derive(Debug, Clone, Default)]
828pub struct HashMapJunction {
829 rows: HashMap<EntityId, Vec<EntityId>>,
830 owners: Option<HashMap<EntityId, Vec<EntityId>>>,
838}
839
840impl HashMapJunction {
841 pub fn exclusive() -> Self {
843 Self {
844 rows: HashMap::new(),
845 owners: Some(HashMap::new()),
846 }
847 }
848
849 pub fn shared() -> Self {
851 Self::default()
852 }
853
854 pub fn is_exclusive(&self) -> bool {
856 self.owners.is_some()
857 }
858
859 pub fn get(&self, source: &EntityId) -> Option<&Vec<EntityId>> {
861 self.rows.get(source)
862 }
863
864 pub fn contains_key(&self, source: &EntityId) -> bool {
866 self.rows.contains_key(source)
867 }
868
869 pub fn iter(&self) -> impl Iterator<Item = (&EntityId, &Vec<EntityId>)> {
871 self.rows.iter()
872 }
873
874 pub fn keys(&self) -> impl Iterator<Item = &EntityId> {
876 self.rows.keys()
877 }
878
879 pub fn values(&self) -> impl Iterator<Item = &Vec<EntityId>> {
881 self.rows.values()
882 }
883
884 pub fn len(&self) -> usize {
886 self.rows.len()
887 }
888
889 pub fn is_empty(&self) -> bool {
891 self.rows.is_empty()
892 }
893
894 pub fn sources_of(&self, target: &EntityId) -> Vec<EntityId> {
898 match &self.owners {
899 Some(owners) => owners.get(target).cloned().unwrap_or_default(),
900 None => self
901 .rows
902 .iter()
903 .flat_map(|(source, targets)| {
904 targets
905 .iter()
906 .filter(|t| *t == target)
907 .map(move |_| *source)
908 })
909 .collect(),
910 }
911 }
912
913 pub fn insert(&mut self, source: EntityId, targets: Vec<EntityId>) -> Option<Vec<EntityId>> {
915 if let Some(owners) = self.owners.as_mut() {
916 let previous = self
917 .rows
918 .get(&source)
919 .map(Vec::as_slice)
920 .unwrap_or_default();
921 reindex(owners, source, previous, &targets);
922 }
923 self.rows.insert(source, targets)
924 }
925
926 pub fn remove(&mut self, source: &EntityId) -> Option<Vec<EntityId>> {
928 let removed = self.rows.remove(source)?;
929 if let Some(owners) = self.owners.as_mut() {
930 reindex(owners, *source, &removed, &[]);
931 }
932 Some(removed)
933 }
934
935 pub fn remove_target(&mut self, target: &EntityId) {
939 let sources: Vec<EntityId> = match self.owners.as_mut() {
940 Some(owners) => {
941 let mut sources = owners.remove(target).unwrap_or_default();
942 sources.sort_unstable();
943 sources.dedup();
944 sources
945 }
946 None => self
947 .rows
948 .iter()
949 .filter(|(_, targets)| targets.contains(target))
950 .map(|(source, _)| *source)
951 .collect(),
952 };
953 for source in sources {
954 if let Some(targets) = self.rows.get_mut(&source) {
955 targets.retain(|t| t != target);
956 }
957 }
958 }
959
960 pub fn check_index(&self) -> Result<(), String> {
966 let Some(owners) = &self.owners else {
967 return Ok(());
968 };
969 let mut expected: std::collections::HashMap<EntityId, Vec<EntityId>> =
970 std::collections::HashMap::new();
971 for (source, targets) in self.rows.iter() {
972 for target in targets {
973 expected.entry(*target).or_default().push(*source);
974 }
975 }
976 if expected.len() != owners.len() {
977 return Err(format!(
978 "the reverse index lists {} targets but the rows hold {}",
979 owners.len(),
980 expected.len()
981 ));
982 }
983 for (target, mut holders) in expected {
984 let mut indexed = owners.get(&target).cloned().unwrap_or_default();
985 holders.sort_unstable();
986 indexed.sort_unstable();
987 if holders != indexed {
988 return Err(format!(
989 "target {target} is held by {holders:?} but indexed under {indexed:?}"
990 ));
991 }
992 }
993 Ok(())
994 }
995}
996
997fn reindex(
1002 owners: &mut HashMap<EntityId, Vec<EntityId>>,
1003 source: EntityId,
1004 previous: &[EntityId],
1005 next: &[EntityId],
1006) {
1007 if previous.is_empty() {
1010 for target in next {
1011 link(owners, source, *target, 1);
1012 }
1013 return;
1014 }
1015 if next.is_empty() {
1016 for target in previous {
1017 unlink(owners, source, *target, 1);
1018 }
1019 return;
1020 }
1021 if previous == next {
1022 return;
1023 }
1024 let mut delta: std::collections::HashMap<EntityId, isize> =
1025 std::collections::HashMap::with_capacity(previous.len() + next.len());
1026 for target in previous {
1027 *delta.entry(*target).or_default() -= 1;
1028 }
1029 for target in next {
1030 *delta.entry(*target).or_default() += 1;
1031 }
1032 for (target, change) in delta {
1033 if change > 0 {
1034 link(owners, source, target, change.unsigned_abs());
1035 } else if change < 0 {
1036 unlink(owners, source, target, change.unsigned_abs());
1037 }
1038 }
1039}
1040
1041fn link(
1043 owners: &mut HashMap<EntityId, Vec<EntityId>>,
1044 source: EntityId,
1045 target: EntityId,
1046 count: usize,
1047) {
1048 owners
1049 .entry(target)
1050 .or_default()
1051 .extend(std::iter::repeat_n(source, count));
1052}
1053
1054fn unlink(
1057 owners: &mut HashMap<EntityId, Vec<EntityId>>,
1058 source: EntityId,
1059 target: EntityId,
1060 count: usize,
1061) {
1062 let emptied = match owners.get_mut(&target) {
1063 Some(holders) => {
1064 for _ in 0..count {
1065 if let Some(at) = holders.iter().position(|h| *h == source) {
1066 holders.swap_remove(at);
1067 }
1068 }
1069 holders.is_empty()
1070 }
1071 None => false,
1072 };
1073 if emptied {
1074 owners.remove(&target);
1075 }
1076}
1077
1078pub(crate) fn read_or_recover<T>(lock: &RwLock<T>) -> std::sync::RwLockReadGuard<'_, T> {
1090 match lock.read() {
1091 Ok(guard) => guard,
1092 Err(poisoned) => {
1093 let guard = poisoned.into_inner();
1094 lock.clear_poison();
1095 guard
1096 }
1097 }
1098}
1099
1100pub(crate) fn write_or_recover<T>(lock: &RwLock<T>) -> std::sync::RwLockWriteGuard<'_, T> {
1102 match lock.write() {
1103 Ok(guard) => guard,
1104 Err(poisoned) => {
1105 let guard = poisoned.into_inner();
1106 lock.clear_poison();
1107 guard
1108 }
1109 }
1110}
1111
1112pub(crate) fn delete_from_backward_junction(junction: &RwLock<HashMapJunction>, id: &EntityId) {
1115 write_or_recover(junction).remove_target(id);
1116}
1117
1118pub(crate) fn junction_get(junction: &RwLock<HashMapJunction>, id: &EntityId) -> Vec<EntityId> {
1119 read_or_recover(junction)
1120 .get(id)
1121 .cloned()
1122 .unwrap_or_default()
1123}
1124
1125pub(crate) fn junction_set(junction: &RwLock<HashMapJunction>, id: EntityId, ids: Vec<EntityId>) {
1126 write_or_recover(junction).insert(id, ids);
1127}
1128
1129pub(crate) fn validate_junction_updates(
1138 junction: &RwLock<HashMapJunction>,
1139 replacements: &[(EntityId, Vec<EntityId>)],
1140 scalar: bool,
1141) -> Result<(), crate::error::RepositoryError> {
1142 use crate::error::RepositoryError::ConstraintViolation;
1143 use std::collections::HashSet;
1144 let mut owners = HashSet::new();
1145 for (owner, targets) in replacements {
1146 if !owners.insert(*owner) {
1147 return Err(ConstraintViolation(
1148 "duplicate source in relationship batch".into(),
1149 ));
1150 }
1151 if scalar && targets.len() > 1 {
1152 return Err(ConstraintViolation(
1153 "to-one relationship accepts at most one target".into(),
1154 ));
1155 }
1156 let mut unique = HashSet::new();
1157 if targets.iter().any(|id| !unique.insert(*id)) {
1158 return Err(ConstraintViolation("duplicate relationship target".into()));
1159 }
1160 }
1161 if replacements.iter().all(|(_, targets)| targets.is_empty()) {
1162 return Ok(());
1163 }
1164 let current = read_or_recover(junction);
1165 let Some(index) = current.owners.as_ref() else {
1166 return Ok(());
1168 };
1169 let mut claimed = HashSet::new();
1173 for (_, targets) in replacements {
1174 for id in targets {
1175 let held_outside = index
1176 .get(id)
1177 .is_some_and(|holders| holders.iter().any(|holder| !owners.contains(holder)));
1178 if held_outside || !claimed.insert(*id) {
1179 return Err(ConstraintViolation(
1180 "relationship target already belongs to another source".into(),
1181 ));
1182 }
1183 }
1184 }
1185 Ok(())
1186}
1187
1188pub(crate) fn junction_remove(junction: &RwLock<HashMapJunction>, id: &EntityId) {
1189 write_or_recover(junction).remove(id);
1190}
1191
1192pub(crate) fn junction_get_relationships_from_right_ids(
1193 junction: &RwLock<HashMapJunction>,
1194 right_ids: &[EntityId],
1195) -> Vec<(EntityId, Vec<EntityId>)> {
1196 let jn = read_or_recover(junction);
1197 jn.iter()
1198 .filter(|(_, rids)| right_ids.iter().any(|eid| rids.contains(eid)))
1199 .map(|(left_id, rids)| (*left_id, rids.clone()))
1200 .collect()
1201}
1202
1203pub(crate) fn junction_move_ids(
1204 junction: &RwLock<HashMapJunction>,
1205 id: &EntityId,
1206 ids_to_move: &[EntityId],
1207 new_index: i32,
1208) -> Vec<EntityId> {
1209 let current = junction_get(junction, id);
1210 if ids_to_move.is_empty() {
1211 return current;
1212 }
1213 let move_set: std::collections::HashSet<EntityId> = ids_to_move.iter().copied().collect();
1214 let mut remaining: Vec<EntityId> = current
1215 .into_iter()
1216 .filter(|eid| !move_set.contains(eid))
1217 .collect();
1218 let insert_pos = if new_index < 0 || (new_index as usize) > remaining.len() {
1219 remaining.len()
1220 } else {
1221 new_index as usize
1222 };
1223 for (i, &eid) in ids_to_move.iter().enumerate() {
1224 remaining.insert(insert_pos + i, eid);
1225 }
1226 junction_set(junction, *id, remaining.clone());
1227 remaining
1228}
1229
1230pub(crate) fn reconcile_backref_list(
1239 live_list: &[EntityId],
1240 snap_list: &[EntityId],
1241 scope: &std::collections::HashSet<EntityId>,
1242) -> Vec<EntityId> {
1243 let snap_members: Vec<EntityId> = snap_list
1245 .iter()
1246 .copied()
1247 .filter(|x| scope.contains(x))
1248 .collect();
1249 let snap_member_set: std::collections::HashSet<EntityId> =
1250 snap_members.iter().copied().collect();
1251
1252 let mut work: Vec<EntityId> = live_list
1254 .iter()
1255 .copied()
1256 .filter(|x| !scope.contains(x) || snap_member_set.contains(x))
1260 .collect();
1261
1262 for id in &snap_members {
1265 if work.contains(id) {
1266 continue;
1267 }
1268 let snap_idx = snap_list.iter().position(|x| x == id).unwrap_or(0);
1269 let pos = snap_list[..snap_idx]
1270 .iter()
1271 .filter(|x| work.contains(x))
1272 .count()
1273 .min(work.len());
1274 work.insert(pos, *id);
1275 }
1276 work
1277}
1278
1279#[macro_export]
1285macro_rules! impl_relationship_methods {
1286 ($table_type:ty, $field_enum:ty) => {
1287 fn get_relationship(
1288 &self,
1289 id: &$crate::types::EntityId,
1290 field: &$field_enum,
1291 ) -> Result<Vec<$crate::types::EntityId>, $crate::error::RepositoryError> {
1292 Ok($crate::database::hashmap_store::junction_get(
1293 self.resolve_junction(field),
1294 id,
1295 ))
1296 }
1297
1298 fn get_relationship_many(
1299 &self,
1300 ids: &[$crate::types::EntityId],
1301 field: &$field_enum,
1302 ) -> Result<
1303 std::collections::HashMap<$crate::types::EntityId, Vec<$crate::types::EntityId>>,
1304 $crate::error::RepositoryError,
1305 > {
1306 let jn = self.resolve_junction(field);
1307 let mut map = std::collections::HashMap::new();
1308 for id in ids {
1309 map.insert(*id, $crate::database::hashmap_store::junction_get(jn, id));
1310 }
1311 Ok(map)
1312 }
1313
1314 fn get_relationship_count(
1315 &self,
1316 id: &$crate::types::EntityId,
1317 field: &$field_enum,
1318 ) -> Result<usize, $crate::error::RepositoryError> {
1319 Ok(
1320 $crate::database::hashmap_store::junction_get(self.resolve_junction(field), id)
1321 .len(),
1322 )
1323 }
1324
1325 fn get_relationship_in_range(
1326 &self,
1327 id: &$crate::types::EntityId,
1328 field: &$field_enum,
1329 offset: usize,
1330 limit: usize,
1331 ) -> Result<Vec<$crate::types::EntityId>, $crate::error::RepositoryError> {
1332 let all =
1333 $crate::database::hashmap_store::junction_get(self.resolve_junction(field), id);
1334 Ok(all.into_iter().skip(offset).take(limit).collect())
1335 }
1336
1337 fn get_relationships_from_right_ids(
1338 &self,
1339 field: &$field_enum,
1340 right_ids: &[$crate::types::EntityId],
1341 ) -> Result<
1342 Vec<($crate::types::EntityId, Vec<$crate::types::EntityId>)>,
1343 $crate::error::RepositoryError,
1344 > {
1345 Ok(
1346 $crate::database::hashmap_store::junction_get_relationships_from_right_ids(
1347 self.resolve_junction(field),
1348 right_ids,
1349 ),
1350 )
1351 }
1352 };
1353}
1354
1355#[macro_export]
1356macro_rules! impl_write_relationship_methods {
1357 ($table_type:ty, $field_enum:ty) => {
1358 $crate::impl_relationship_methods!($table_type, $field_enum);
1359
1360 fn set_relationship_multi(
1361 &mut self,
1362 field: &$field_enum,
1363 relationships: Vec<($crate::types::EntityId, Vec<$crate::types::EntityId>)>,
1364 ) -> Result<(), $crate::error::RepositoryError> {
1365 for (id, _) in &relationships {
1366 if !self.relationship_source_exists(id) {
1367 return Err($crate::error::RepositoryError::ConstraintViolation(
1368 "relationship source does not exist".into(),
1369 ));
1370 }
1371 }
1372 self.validate_relationship_updates(field, &relationships, &[])?;
1373 let jn = self.resolve_junction(field);
1374 for (left_id, entities) in relationships {
1375 $crate::database::hashmap_store::junction_set(jn, left_id, entities);
1376 }
1377 Ok(())
1378 }
1379
1380 fn set_relationship(
1381 &mut self,
1382 id: &$crate::types::EntityId,
1383 field: &$field_enum,
1384 right_ids: &[$crate::types::EntityId],
1385 ) -> Result<(), $crate::error::RepositoryError> {
1386 self.set_relationship_multi(field, vec![(*id, right_ids.to_vec())])
1387 }
1388
1389 fn move_relationship_ids(
1390 &mut self,
1391 id: &$crate::types::EntityId,
1392 field: &$field_enum,
1393 ids_to_move: &[$crate::types::EntityId],
1394 new_index: i32,
1395 ) -> Result<Vec<$crate::types::EntityId>, $crate::error::RepositoryError> {
1396 if !self.relationship_is_ordered(field) {
1397 return Err($crate::error::RepositoryError::ConstraintViolation(
1398 "only ordered relationships can be moved".into(),
1399 ));
1400 }
1401 let current =
1402 $crate::database::hashmap_store::junction_get(self.resolve_junction(field), id);
1403 let mut seen = std::collections::HashSet::new();
1404 if ids_to_move
1405 .iter()
1406 .any(|target| !seen.insert(*target) || !current.contains(target))
1407 {
1408 return Err($crate::error::RepositoryError::ConstraintViolation(
1409 "move requires distinct existing relationship members".into(),
1410 ));
1411 }
1412 self.validate_relationship_updates(field, &[(*id, current)], &[])?;
1413 if !self.relationship_source_exists(id) {
1414 return Err($crate::error::RepositoryError::ConstraintViolation(
1415 "relationship source does not exist".into(),
1416 ));
1417 }
1418 Ok($crate::database::hashmap_store::junction_move_ids(
1419 self.resolve_junction(field),
1420 id,
1421 ids_to_move,
1422 new_index,
1423 ))
1424 }
1425 };
1426}
1427
1428#[macro_export]
1433macro_rules! impl_leaf_entity_table {
1434 (
1435 entity: $Entity:ident,
1436 entity_name: $entity_name:expr,
1437 store_field: $store_field:ident,
1438 table_trait: $TableTrait:ident,
1439 table_ro_trait: $TableROTrait:ident,
1440 table_struct: $TableStruct:ident,
1441 table_ro_struct: $TableROStruct:ident,
1442 backward_junctions: [ $( ($bj_field:ident) ),* $(,)? ],
1443 ) => {
1444 pub struct $TableStruct<'a> {
1445 store: &'a $crate::database::hashmap_store::HashMapStore,
1446 }
1447
1448 impl<'a> $TableStruct<'a> {
1449 pub fn new(store: &'a $crate::database::hashmap_store::HashMapStore) -> Self {
1450 Self { store }
1451 }
1452 }
1453
1454 impl<'a> $TableTrait for $TableStruct<'a> {
1455 fn create(&mut self, entity: &$Entity) -> Result<$Entity, $crate::error::RepositoryError> {
1456 self.create_multi(std::slice::from_ref(entity))
1457 .and_then(|v| {
1458 v.into_iter().next().ok_or_else(|| {
1459 $crate::error::RepositoryError::Other(::anyhow::anyhow!(
1460 "create: the store returned no row for the entity it was given"
1461 ))
1462 })
1463 })
1464 }
1465
1466 fn create_multi(&mut self, entities: &[$Entity]) -> Result<Vec<$Entity>, $crate::error::RepositoryError> {
1467 let mut created = Vec::with_capacity(entities.len());
1468 let mut map = $crate::database::hashmap_store::write_or_recover(&self.store.$store_field);
1469
1470 for entity in entities {
1471 let new_entity = if entity.id == $crate::types::EntityId::default() {
1472 let id = self.store.next_id($entity_name);
1473 $Entity {
1474 id,
1475 ..entity.clone()
1476 }
1477 } else {
1478 if map.contains_key(&entity.id) {
1479 return Err($crate::error::RepositoryError::DuplicateId {
1480 entity: stringify!($Entity),
1481 id: entity.id,
1482 });
1483 }
1484 entity.clone()
1485 };
1486
1487 map.insert(new_entity.id, new_entity.clone());
1488 created.push(new_entity);
1489 }
1490 Ok(created)
1491 }
1492
1493 fn get(&self, id: &$crate::types::EntityId) -> Result<Option<$Entity>, $crate::error::RepositoryError> {
1494 Ok($crate::database::hashmap_store::read_or_recover(&self.store.$store_field).get(id).cloned())
1495 }
1496
1497 fn get_multi(&self, ids: &[$crate::types::EntityId]) -> Result<Vec<Option<$Entity>>, $crate::error::RepositoryError> {
1498 let map = $crate::database::hashmap_store::read_or_recover(&self.store.$store_field);
1499 Ok(ids.iter().map(|id| map.get(id).cloned()).collect())
1500 }
1501
1502 fn get_all(&self) -> Result<Vec<$Entity>, $crate::error::RepositoryError> {
1503 Ok($crate::database::hashmap_store::read_or_recover(&self.store.$store_field).values().cloned().collect())
1504 }
1505
1506 fn update(&mut self, entity: &$Entity) -> Result<$Entity, $crate::error::RepositoryError> {
1507 self.update_multi(std::slice::from_ref(entity))
1508 .and_then(|v| {
1509 v.into_iter().next().ok_or_else(|| {
1510 $crate::error::RepositoryError::Other(::anyhow::anyhow!(
1511 "update: the store returned no row for the entity it was given"
1512 ))
1513 })
1514 })
1515 }
1516
1517 fn update_multi(&mut self, entities: &[$Entity]) -> Result<Vec<$Entity>, $crate::error::RepositoryError> {
1518 let mut map = $crate::database::hashmap_store::write_or_recover(&self.store.$store_field);
1519 let mut result = Vec::with_capacity(entities.len());
1520 for entity in entities {
1521 map.insert(entity.id, entity.clone());
1522 result.push(entity.clone());
1523 }
1524 Ok(result)
1525 }
1526
1527 fn update_with_relationships(&mut self, entity: &$Entity) -> Result<$Entity, $crate::error::RepositoryError> {
1528 self.update(entity)
1529 }
1530
1531 fn update_with_relationships_multi(&mut self, entities: &[$Entity]) -> Result<Vec<$Entity>, $crate::error::RepositoryError> {
1532 self.update_multi(entities)
1533 }
1534
1535 fn remove(&mut self, id: &$crate::types::EntityId) -> Result<(), $crate::error::RepositoryError> {
1536 self.remove_multi(std::slice::from_ref(id))
1537 }
1538
1539 fn remove_multi(&mut self, ids: &[$crate::types::EntityId]) -> Result<(), $crate::error::RepositoryError> {
1540 let mut map = $crate::database::hashmap_store::write_or_recover(&self.store.$store_field);
1541 for id in ids {
1542 map.remove(id);
1543 $(
1544 $crate::database::hashmap_store::delete_from_backward_junction(&self.store.$bj_field, id);
1545 )*
1546 }
1547 Ok(())
1548 }
1549 }
1550
1551 pub struct $TableROStruct<'a> {
1552 store: &'a $crate::database::hashmap_store::HashMapStore,
1553 }
1554
1555 impl<'a> $TableROStruct<'a> {
1556 pub fn new(store: &'a $crate::database::hashmap_store::HashMapStore) -> Self {
1557 Self { store }
1558 }
1559 }
1560
1561 impl<'a> $TableROTrait for $TableROStruct<'a> {
1562 fn get(&self, id: &$crate::types::EntityId) -> Result<Option<$Entity>, $crate::error::RepositoryError> {
1563 Ok($crate::database::hashmap_store::read_or_recover(&self.store.$store_field).get(id).cloned())
1564 }
1565
1566 fn get_multi(&self, ids: &[$crate::types::EntityId]) -> Result<Vec<Option<$Entity>>, $crate::error::RepositoryError> {
1567 let map = $crate::database::hashmap_store::read_or_recover(&self.store.$store_field);
1568 Ok(ids.iter().map(|id| map.get(id).cloned()).collect())
1569 }
1570
1571 fn get_all(&self) -> Result<Vec<$Entity>, $crate::error::RepositoryError> {
1572 Ok($crate::database::hashmap_store::read_or_recover(&self.store.$store_field).values().cloned().collect())
1573 }
1574 }
1575 };
1576}
1577
1578#[cfg(test)]
1583mod junction_tests {
1584 use super::*;
1585 use crate::error::RepositoryError;
1586
1587 fn validate_by_walking(
1591 junction: &HashMapJunction,
1592 replacements: &[(EntityId, Vec<EntityId>)],
1593 scalar: bool,
1594 ) -> Result<(), RepositoryError> {
1595 use RepositoryError::ConstraintViolation;
1596 use std::collections::HashSet;
1597 let mut owners = HashSet::new();
1598 for (owner, targets) in replacements {
1599 if !owners.insert(*owner) {
1600 return Err(ConstraintViolation(
1601 "duplicate source in relationship batch".into(),
1602 ));
1603 }
1604 if scalar && targets.len() > 1 {
1605 return Err(ConstraintViolation(
1606 "to-one relationship accepts at most one target".into(),
1607 ));
1608 }
1609 let mut unique = HashSet::new();
1610 if targets.iter().any(|id| !unique.insert(*id)) {
1611 return Err(ConstraintViolation("duplicate relationship target".into()));
1612 }
1613 }
1614 if junction.is_exclusive() {
1615 let mut used = HashSet::new();
1616 for (_, targets) in junction.iter().filter(|(id, _)| !owners.contains(id)) {
1617 used.extend(targets.iter().copied());
1618 }
1619 for (_, targets) in replacements {
1620 if targets.iter().any(|id| !used.insert(*id)) {
1621 return Err(ConstraintViolation(
1622 "relationship target already belongs to another source".into(),
1623 ));
1624 }
1625 }
1626 }
1627 Ok(())
1628 }
1629
1630 struct Rng(u64);
1632
1633 impl Rng {
1634 fn below(&mut self, bound: u64) -> u64 {
1635 self.0 ^= self.0 << 13;
1636 self.0 ^= self.0 >> 7;
1637 self.0 ^= self.0 << 17;
1638 self.0 % bound
1639 }
1640
1641 fn ids(&mut self, max_len: u64, bound: u64) -> Vec<EntityId> {
1642 (0..self.below(max_len + 1))
1643 .map(|_| self.below(bound))
1644 .collect()
1645 }
1646 }
1647
1648 #[test]
1653 fn the_index_stays_exact_and_the_check_matches_the_walk() {
1654 for exclusive in [true, false] {
1655 let mut rng = Rng(0x9E37_79B9_7F4A_7C15 + u64::from(exclusive));
1656 let junction = RwLock::new(if exclusive {
1657 HashMapJunction::exclusive()
1658 } else {
1659 HashMapJunction::shared()
1660 });
1661 for step in 0..3000 {
1662 {
1663 let mut jn = write_or_recover(&junction);
1664 match rng.below(6) {
1665 0..=2 => {
1666 let (source, targets) = (rng.below(12), rng.ids(4, 16));
1667 jn.insert(source, targets);
1668 }
1669 3 => {
1670 jn.remove(&rng.below(12));
1671 }
1672 4 => jn.remove_target(&rng.below(16)),
1673 _ => {
1675 let copy = jn.clone();
1676 *jn = copy;
1677 }
1678 }
1679 if let Err(problem) = jn.check_index() {
1680 panic!("exclusive={exclusive}, step {step}: {problem}");
1681 }
1682 }
1683 let batch: Vec<(EntityId, Vec<EntityId>)> = (0..=rng.below(3))
1684 .map(|_| (rng.below(12), rng.ids(2, 16)))
1685 .collect();
1686 for scalar in [false, true] {
1687 let walked = validate_by_walking(&read_or_recover(&junction), &batch, scalar);
1688 let indexed = validate_junction_updates(&junction, &batch, scalar);
1689 assert_eq!(
1690 format!("{indexed:?}"),
1691 format!("{walked:?}"),
1692 "exclusive={exclusive}, step {step}, scalar={scalar}, batch {batch:?}"
1693 );
1694 }
1695 }
1696 }
1697 }
1698
1699 #[test]
1700 fn a_transfer_within_one_batch_is_valid_but_not_from_outside_it() {
1701 let junction = RwLock::new(HashMapJunction::exclusive());
1702 junction_set(&junction, 1, vec![10, 11]);
1703 junction_set(&junction, 2, vec![20]);
1704 let check = |batch: &[(EntityId, Vec<EntityId>)]| {
1705 validate_junction_updates(&junction, batch, false)
1706 };
1707 assert!(check(&[(1, vec![11]), (2, vec![20, 10])]).is_ok());
1708 assert!(check(&[(2, vec![20, 10])]).is_err());
1709 assert!(check(&[(3, vec![10])]).is_err());
1710 assert!(check(&[(1, vec![]), (2, vec![10]), (3, vec![10])]).is_err());
1711 assert!(check(&[(3, vec![]), (4, vec![])]).is_ok());
1713 junction_remove(&junction, &1);
1715 assert!(check(&[(3, vec![10])]).is_ok());
1716 delete_from_backward_junction(&junction, &20);
1717 assert!(check(&[(3, vec![20])]).is_ok());
1718 assert_eq!(junction_get(&junction, &2), Vec::<EntityId>::new());
1719 assert!(read_or_recover(&junction).check_index().is_ok());
1720 }
1721}