1use std::any::{Any, TypeId};
2use std::borrow::Cow;
3use std::collections::{BTreeMap, BTreeSet, HashMap};
4use std::sync::{Arc, Mutex, OnceLock};
5
6use teaql_core::{EntitySnapshot, MutationValues, SmartList, Value};
7
8#[derive(Debug, Clone)]
9pub struct EntityKey {
10 pub entity: Cow<'static, str>,
11 pub id: Value,
12 id_key: EntityIdentityKey,
13}
14
15#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
16enum EntityIdentityKey {
17 Null,
18 Bool(bool),
19 I64(i64),
20 U64(u64),
21 F64(u64),
22 Decimal(rust_decimal::Decimal),
23 Text(String),
24 Date(chrono::NaiveDate),
25 Timestamp(i64),
26 Other(String),
27}
28
29impl EntityKey {
30 pub fn new(entity: impl Into<String>, id: impl Into<Value>) -> Self {
31 let id = id.into();
32 Self {
33 entity: Cow::Owned(entity.into()),
34 id_key: entity_identity_key(&id),
35 id,
36 }
37 }
38
39 pub fn new_static(entity: &'static str, id: impl Into<Value>) -> Self {
40 let id = id.into();
41 Self {
42 entity: Cow::Borrowed(entity),
43 id_key: entity_identity_key(&id),
44 id,
45 }
46 }
47}
48
49impl PartialEq for EntityKey {
50 fn eq(&self, other: &Self) -> bool {
51 self.entity == other.entity && self.id_key == other.id_key
52 }
53}
54
55impl Eq for EntityKey {}
56
57impl PartialOrd for EntityKey {
58 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
59 Some(self.cmp(other))
60 }
61}
62
63impl Ord for EntityKey {
64 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
65 self.entity
66 .cmp(&other.entity)
67 .then_with(|| self.id_key.cmp(&other.id_key))
68 }
69}
70
71fn entity_identity_key(value: &Value) -> EntityIdentityKey {
72 match value {
73 Value::Null | Value::TypedNull(_) => EntityIdentityKey::Null,
74 Value::Bool(value) => EntityIdentityKey::Bool(*value),
75 Value::I64(value) => EntityIdentityKey::I64(*value),
76 Value::U64(value) => EntityIdentityKey::U64(*value),
77 Value::F64(value) => EntityIdentityKey::F64(value.to_bits()),
78 Value::Decimal(value) => EntityIdentityKey::Decimal(*value),
79 Value::Text(value) => EntityIdentityKey::Text(value.clone()),
80 Value::Json(value) => EntityIdentityKey::Other(format!("json:{value}")),
81 Value::Date(value) => EntityIdentityKey::Date(*value),
82 Value::Timestamp(value) => EntityIdentityKey::Timestamp(value.0),
83 Value::Object(_) => EntityIdentityKey::Other("object".to_owned()),
84 Value::List(_) => EntityIdentityKey::Other("list".to_owned()),
85 }
86}
87
88#[derive(Default)]
89pub struct EntityGraphBuilder {
90 tables: HashMap<TypeId, HashMap<u64, Box<dyn Any + Send + Sync>>>,
91 relation_lists: HashMap<RelationListKey, Box<dyn Any + Send + Sync>>,
92}
93
94#[derive(Debug, Clone, PartialEq, Eq, Hash)]
95struct RelationListKey {
96 owner_entity: String,
97 owner_id: u64,
98 relation: String,
99}
100
101impl EntityGraphBuilder {
102 pub fn install<T>(&mut self, id: u64, entity: T)
103 where
104 T: Any + Send + Sync,
105 {
106 self.tables
107 .entry(TypeId::of::<T>())
108 .or_default()
109 .insert(id, Box::new(entity));
110 }
111
112 pub fn entity_count(&self) -> usize {
113 self.tables.values().map(HashMap::len).sum()
114 }
115
116 pub fn install_relation_list<T>(
117 &mut self,
118 owner_entity: impl Into<String>,
119 owner_id: u64,
120 relation: impl Into<String>,
121 list: SmartList<T>,
122 ) where
123 T: Any + Send + Sync,
124 {
125 self.relation_lists.insert(
126 RelationListKey {
127 owner_entity: crate::canonical_id_space_entity(&owner_entity.into()),
128 owner_id,
129 relation: relation.into(),
130 },
131 Box::new(list),
132 );
133 }
134
135 pub fn install_relation_option<T>(
136 &mut self,
137 owner_entity: impl Into<String>,
138 owner_id: u64,
139 relation: impl Into<String>,
140 value: Option<T>,
141 ) where
142 T: Any + Send + Sync,
143 {
144 self.relation_lists.insert(
145 RelationListKey {
146 owner_entity: crate::canonical_id_space_entity(&owner_entity.into()),
147 owner_id,
148 relation: relation.into(),
149 },
150 Box::new(value),
151 );
152 }
153
154 pub fn relation_list_count(&self) -> usize {
155 self.relation_lists.len()
156 }
157
158 fn freeze(self) -> FrozenEntityGraph {
159 FrozenEntityGraph {
160 tables: self.tables,
161 relation_lists: self.relation_lists,
162 }
163 }
164}
165
166impl std::fmt::Debug for EntityGraphBuilder {
167 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168 formatter
169 .debug_struct("EntityGraphBuilder")
170 .field("entity_types", &self.tables.len())
171 .field("entities", &self.entity_count())
172 .field("relation_lists", &self.relation_list_count())
173 .finish()
174 }
175}
176
177struct FrozenEntityGraph {
178 tables: HashMap<TypeId, HashMap<u64, Box<dyn Any + Send + Sync>>>,
179 relation_lists: HashMap<RelationListKey, Box<dyn Any + Send + Sync>>,
180}
181
182impl std::fmt::Debug for FrozenEntityGraph {
183 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184 formatter
185 .debug_struct("FrozenEntityGraph")
186 .field("entity_types", &self.tables.len())
187 .field(
188 "entities",
189 &self.tables.values().map(HashMap::len).sum::<usize>(),
190 )
191 .field("relation_lists", &self.relation_lists.len())
192 .finish()
193 }
194}
195
196#[derive(Debug, Clone, Default, PartialEq)]
197pub struct EntityChangeSet {
198 changes: BTreeMap<EntityKey, MutationValues>,
199}
200
201#[derive(Debug, Default)]
202struct OriginalVersions {
203 first: Option<(EntityKey, i64)>,
204 overflow: BTreeMap<EntityKey, i64>,
205}
206
207impl OriginalVersions {
208 fn clear(&mut self) {
209 self.first = None;
210 self.overflow.clear();
211 }
212
213 fn get(&self, key: &EntityKey) -> Option<i64> {
214 self.first
215 .as_ref()
216 .and_then(|(first_key, version)| (first_key == key).then_some(*version))
217 .or_else(|| self.overflow.get(key).copied())
218 }
219
220 fn insert(&mut self, key: EntityKey, version: i64) {
221 match &mut self.first {
222 None => self.first = Some((key, version)),
223 Some((first_key, first_version)) if first_key == &key => *first_version = version,
224 Some(_) => {
225 self.overflow.insert(key, version);
226 }
227 }
228 }
229}
230
231impl EntityChangeSet {
232 pub fn is_empty(&self) -> bool {
233 self.changes.is_empty()
234 }
235
236 pub fn set(&mut self, key: EntityKey, field: impl Into<String>, value: Value) {
237 self.changes
238 .entry(key)
239 .or_default()
240 .insert(field.into(), value);
241 }
242
243 pub fn get(&self, key: &EntityKey, field: &str) -> Option<&Value> {
244 self.changes.get(key).and_then(|changes| changes.get(field))
245 }
246
247 pub fn changes(&self) -> &BTreeMap<EntityKey, MutationValues> {
248 &self.changes
249 }
250
251 pub fn clear_entity(&mut self, key: &EntityKey) {
253 self.changes.remove(key);
254 }
255
256 pub fn field_names(&self, key: &EntityKey) -> BTreeSet<String> {
258 self.changes
259 .get(key)
260 .map(|record| record.keys().cloned().collect())
261 .unwrap_or_default()
262 }
263}
264
265#[derive(Debug, Clone, Default, PartialEq)]
266pub struct ChangeSetStack {
267 stack: Vec<EntityChangeSet>,
268}
269
270impl ChangeSetStack {
271 pub fn current_mut(&mut self) -> &mut EntityChangeSet {
272 if self.stack.is_empty() {
273 self.stack.push(EntityChangeSet::default());
274 }
275 self.stack.last_mut().expect("change set stack has current")
276 }
277
278 pub fn current(&self) -> Option<&EntityChangeSet> {
279 self.stack.last()
280 }
281
282 pub fn push(&mut self) {
283 self.stack.push(EntityChangeSet::default());
284 }
285
286 pub fn pop(&mut self) -> Option<EntityChangeSet> {
287 self.stack.pop()
288 }
289
290 pub fn get(&self, key: &EntityKey, field: &str) -> Option<Value> {
291 self.stack
292 .iter()
293 .rev()
294 .find_map(|change_set| change_set.get(key, field).cloned())
295 }
296
297 pub fn set(&mut self, key: EntityKey, field: impl Into<String>, value: Value) {
298 self.current_mut().set(key, field, value);
299 }
300
301 pub fn clear_current(&mut self) {
302 if let Some(current) = self.stack.last_mut() {
303 *current = EntityChangeSet::default();
304 }
305 }
306
307 pub fn clear_entity(&mut self, key: &EntityKey) {
309 for change_set in &mut self.stack {
310 change_set.clear_entity(key);
311 }
312 }
313
314 pub fn changed_field_names(&self, key: &EntityKey) -> BTreeSet<String> {
317 let mut fields = BTreeSet::new();
318 for change_set in &self.stack {
319 fields.extend(change_set.field_names(key));
320 }
321 fields
322 }
323}
324
325#[derive(Debug, Default)]
326pub struct RootContext {
327 change_sets: ChangeSetStack,
328 comment: Option<String>,
330 deleted_keys: std::collections::BTreeSet<EntityKey>,
333 new_keys: std::collections::BTreeSet<EntityKey>,
335 original_snapshot: Option<OriginalSnapshot>,
337 trace_chains: std::collections::BTreeMap<EntityKey, Vec<teaql_core::TraceNode>>,
339 original_versions: OriginalVersions,
341 is_new: bool,
343}
344
345#[derive(Debug, Clone, Default)]
346pub struct EntityRoot {
347 inner: Arc<Mutex<RootContext>>,
348 graph: Arc<OnceLock<FrozenEntityGraph>>,
349}
350
351impl std::panic::UnwindSafe for EntityRoot {}
352impl std::panic::RefUnwindSafe for EntityRoot {}
353
354#[derive(Debug)]
355enum OriginalSnapshot {
356 Materialized(EntitySnapshot),
357 Compact(teaql_core::CompactRow),
358}
359
360impl PartialEq for EntityRoot {
361 fn eq(&self, other: &Self) -> bool {
362 Arc::ptr_eq(&self.inner, &other.inner)
363 }
364}
365
366impl EntityRoot {
367 pub fn with_shared_graph(&self, source: &EntityRoot) -> Self {
370 Self {
371 inner: self.inner.clone(),
372 graph: source.graph.clone(),
373 }
374 }
375
376 pub fn freeze_graph(&self, builder: EntityGraphBuilder) -> Result<(), EntityGraphBuilder> {
378 self.graph
379 .set(builder.freeze())
380 .map_err(|graph| EntityGraphBuilder {
381 tables: graph.tables,
382 relation_lists: graph.relation_lists,
383 })
384 }
385
386 pub fn resolve_entity<T>(&self, id: u64) -> Option<&T>
388 where
389 T: Any + Send + Sync,
390 {
391 self.graph
392 .get()?
393 .tables
394 .get(&TypeId::of::<T>())?
395 .get(&id)?
396 .downcast_ref::<T>()
397 }
398
399 pub fn resolve_relation_list<T>(
400 &self,
401 owner_entity: &str,
402 owner_id: u64,
403 relation: &str,
404 ) -> Option<&SmartList<T>>
405 where
406 T: Any + Send + Sync,
407 {
408 self.graph
409 .get()?
410 .relation_lists
411 .get(&RelationListKey {
412 owner_entity: crate::canonical_id_space_entity(owner_entity),
413 owner_id,
414 relation: relation.to_owned(),
415 })?
416 .downcast_ref::<SmartList<T>>()
417 }
418
419 pub fn resolve_relation_option<T>(
420 &self,
421 owner_entity: &str,
422 owner_id: u64,
423 relation: &str,
424 ) -> Option<&Option<T>>
425 where
426 T: Any + Send + Sync,
427 {
428 self.graph
429 .get()?
430 .relation_lists
431 .get(&RelationListKey {
432 owner_entity: crate::canonical_id_space_entity(owner_entity),
433 owner_id,
434 relation: relation.to_owned(),
435 })?
436 .downcast_ref::<Option<T>>()
437 }
438
439 pub fn has_relation_view(&self, owner_entity: &str, owner_id: u64, relation: &str) -> bool {
440 self.graph.get().is_some_and(|graph| {
441 graph.relation_lists.contains_key(&RelationListKey {
442 owner_entity: crate::canonical_id_space_entity(owner_entity),
443 owner_id,
444 relation: relation.to_owned(),
445 })
446 })
447 }
448
449 pub fn push_change_set(&self) {
450 self.inner
451 .lock()
452 .unwrap_or_else(|e| e.into_inner())
453 .change_sets
454 .push();
455 }
456
457 pub fn pop_change_set(&self) -> Option<EntityChangeSet> {
458 self.inner
459 .lock()
460 .unwrap_or_else(|e| e.into_inner())
461 .change_sets
462 .pop()
463 }
464
465 pub fn clear_current_change_set(&self) {
466 self.inner
467 .lock()
468 .unwrap_or_else(|e| e.into_inner())
469 .change_sets
470 .clear_current();
471 }
472
473 pub fn clear_committed(&self) {
476 let mut context = self.inner.lock().unwrap_or_else(|e| e.into_inner());
477 context.change_sets = ChangeSetStack::default();
478 context.deleted_keys.clear();
479 context.new_keys.clear();
480 context.original_versions.clear();
481 context.trace_chains.clear();
482 context.original_snapshot = None;
483 context.comment = None;
484 context.is_new = false;
485 }
486
487 pub fn set(&self, key: EntityKey, field: impl Into<String>, value: impl Into<Value>) {
488 self.inner
489 .lock()
490 .unwrap_or_else(|e| e.into_inner())
491 .change_sets
492 .set(key, field, value.into());
493 }
494
495 pub fn get(&self, key: &EntityKey, field: &str) -> Option<Value> {
496 self.inner
497 .lock()
498 .unwrap_or_else(|e| e.into_inner())
499 .change_sets
500 .get(key, field)
501 }
502
503 pub fn current_change_set(&self) -> EntityChangeSet {
504 self.inner
505 .lock()
506 .unwrap_or_else(|e| e.into_inner())
507 .change_sets
508 .current()
509 .cloned()
510 .unwrap_or_default()
511 }
512
513 pub fn set_comment(&self, comment: impl Into<String>) {
516 self.inner.lock().unwrap_or_else(|e| e.into_inner()).comment = Some(comment.into());
517 }
518
519 pub fn get_comment(&self) -> Option<String> {
521 self.inner
522 .lock()
523 .unwrap_or_else(|e| e.into_inner())
524 .comment
525 .clone()
526 }
527
528 pub fn mark_as_new(&self, key: EntityKey) {
530 self.inner
531 .lock()
532 .unwrap_or_else(|e| e.into_inner())
533 .new_keys
534 .insert(key);
535 }
536
537 pub fn is_new(&self, key: &EntityKey) -> bool {
539 self.inner
540 .lock()
541 .unwrap_or_else(|e| e.into_inner())
542 .new_keys
543 .contains(key)
544 }
545
546 pub fn set_original_snapshot(&self, snapshot: EntitySnapshot) {
548 self.inner
549 .lock()
550 .unwrap_or_else(|e| e.into_inner())
551 .original_snapshot = Some(OriginalSnapshot::Materialized(snapshot));
552 }
553
554 pub fn set_original_compact_row(&self, row: teaql_core::CompactRow) {
556 self.inner
557 .lock()
558 .unwrap_or_else(|e| e.into_inner())
559 .original_snapshot = Some(OriginalSnapshot::Compact(row));
560 }
561
562 pub fn original_snapshot(&self) -> Option<EntitySnapshot> {
564 self.inner
565 .lock()
566 .unwrap_or_else(|e| e.into_inner())
567 .original_snapshot
568 .as_ref()
569 .map(|snapshot| match snapshot {
570 OriginalSnapshot::Materialized(snapshot) => snapshot.clone(),
571 OriginalSnapshot::Compact(row) => EntitySnapshot::from(row.clone().into_map()),
572 })
573 }
574
575 pub fn mark_as_delete(&self, key: EntityKey) {
580 let mut context = self.inner.lock().unwrap_or_else(|e| e.into_inner());
581 context.change_sets.clear_entity(&key);
582 context.deleted_keys.insert(key);
583 }
584
585 pub fn is_marked_as_delete(&self, key: &EntityKey) -> bool {
587 self.inner
588 .lock()
589 .unwrap_or_else(|e| e.into_inner())
590 .deleted_keys
591 .contains(key)
592 }
593
594 pub fn changed_field_names(&self, key: &EntityKey) -> BTreeSet<String> {
597 self.inner
598 .lock()
599 .unwrap_or_else(|e| e.into_inner())
600 .change_sets
601 .changed_field_names(key)
602 }
603 pub fn deleted_keys(&self) -> std::collections::BTreeSet<EntityKey> {
604 self.inner
605 .lock()
606 .unwrap_or_else(|e| e.into_inner())
607 .deleted_keys
608 .clone()
609 }
610
611 pub fn new_keys(&self) -> std::collections::BTreeSet<EntityKey> {
612 self.inner
613 .lock()
614 .unwrap_or_else(|e| e.into_inner())
615 .new_keys
616 .clone()
617 }
618
619 pub fn get_original_version(&self, key: &EntityKey) -> Option<i64> {
620 self.inner
621 .lock()
622 .unwrap_or_else(|e| e.into_inner())
623 .original_versions
624 .get(key)
625 }
626
627 pub fn get_trace_chain(&self, key: &EntityKey) -> Vec<teaql_core::TraceNode> {
628 self.inner
629 .lock()
630 .unwrap_or_else(|e| e.into_inner())
631 .trace_chains
632 .get(key)
633 .cloned()
634 .unwrap_or_default()
635 }
636
637 pub fn set_original_version(&self, key: EntityKey, version: i64) {
638 self.inner
639 .lock()
640 .unwrap_or_else(|e| e.into_inner())
641 .original_versions
642 .insert(key, version);
643 }
644}
645
646pub trait LedgerEntity: teaql_core::Entity {
647 fn entity_root(&self) -> Option<EntityRoot>;
648}