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: 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: 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
351#[derive(Debug)]
352enum OriginalSnapshot {
353 Materialized(EntitySnapshot),
354 Compact(teaql_core::CompactRow),
355}
356
357impl PartialEq for EntityRoot {
358 fn eq(&self, other: &Self) -> bool {
359 Arc::ptr_eq(&self.inner, &other.inner)
360 }
361}
362
363impl EntityRoot {
364 pub fn with_shared_graph(&self, source: &EntityRoot) -> Self {
367 Self {
368 inner: self.inner.clone(),
369 graph: source.graph.clone(),
370 }
371 }
372
373 pub fn freeze_graph(&self, builder: EntityGraphBuilder) -> Result<(), EntityGraphBuilder> {
375 self.graph
376 .set(builder.freeze())
377 .map_err(|graph| EntityGraphBuilder {
378 tables: graph.tables,
379 relation_lists: graph.relation_lists,
380 })
381 }
382
383 pub fn resolve_entity<T>(&self, id: u64) -> Option<&T>
385 where
386 T: Any + Send + Sync,
387 {
388 self.graph
389 .get()?
390 .tables
391 .get(&TypeId::of::<T>())?
392 .get(&id)?
393 .downcast_ref::<T>()
394 }
395
396 pub fn resolve_relation_list<T>(
397 &self,
398 owner_entity: &str,
399 owner_id: u64,
400 relation: &str,
401 ) -> Option<&SmartList<T>>
402 where
403 T: Any + Send + Sync,
404 {
405 self.graph
406 .get()?
407 .relation_lists
408 .get(&RelationListKey {
409 owner_entity: owner_entity.to_owned(),
410 owner_id,
411 relation: relation.to_owned(),
412 })?
413 .downcast_ref::<SmartList<T>>()
414 }
415
416 pub fn resolve_relation_option<T>(
417 &self,
418 owner_entity: &str,
419 owner_id: u64,
420 relation: &str,
421 ) -> Option<&Option<T>>
422 where
423 T: Any + Send + Sync,
424 {
425 self.graph
426 .get()?
427 .relation_lists
428 .get(&RelationListKey {
429 owner_entity: owner_entity.to_owned(),
430 owner_id,
431 relation: relation.to_owned(),
432 })?
433 .downcast_ref::<Option<T>>()
434 }
435
436 pub fn has_relation_view(&self, owner_entity: &str, owner_id: u64, relation: &str) -> bool {
437 self.graph.get().is_some_and(|graph| {
438 graph.relation_lists.contains_key(&RelationListKey {
439 owner_entity: owner_entity.to_owned(),
440 owner_id,
441 relation: relation.to_owned(),
442 })
443 })
444 }
445
446 pub fn push_change_set(&self) {
447 self.inner
448 .lock()
449 .unwrap_or_else(|e| e.into_inner())
450 .change_sets
451 .push();
452 }
453
454 pub fn pop_change_set(&self) -> Option<EntityChangeSet> {
455 self.inner
456 .lock()
457 .unwrap_or_else(|e| e.into_inner())
458 .change_sets
459 .pop()
460 }
461
462 pub fn clear_current_change_set(&self) {
463 self.inner
464 .lock()
465 .unwrap_or_else(|e| e.into_inner())
466 .change_sets
467 .clear_current();
468 }
469
470 pub fn clear_committed(&self) {
473 let mut context = self.inner.lock().unwrap_or_else(|e| e.into_inner());
474 context.change_sets = ChangeSetStack::default();
475 context.deleted_keys.clear();
476 context.new_keys.clear();
477 context.original_versions.clear();
478 context.trace_chains.clear();
479 context.original_snapshot = None;
480 context.comment = None;
481 context.is_new = false;
482 }
483
484 pub fn set(&self, key: EntityKey, field: impl Into<String>, value: impl Into<Value>) {
485 self.inner
486 .lock()
487 .unwrap_or_else(|e| e.into_inner())
488 .change_sets
489 .set(key, field, value.into());
490 }
491
492 pub fn get(&self, key: &EntityKey, field: &str) -> Option<Value> {
493 self.inner
494 .lock()
495 .unwrap_or_else(|e| e.into_inner())
496 .change_sets
497 .get(key, field)
498 }
499
500 pub fn current_change_set(&self) -> EntityChangeSet {
501 self.inner
502 .lock()
503 .unwrap_or_else(|e| e.into_inner())
504 .change_sets
505 .current()
506 .cloned()
507 .unwrap_or_default()
508 }
509
510 pub fn set_comment(&self, comment: impl Into<String>) {
513 self.inner.lock().unwrap_or_else(|e| e.into_inner()).comment = Some(comment.into());
514 }
515
516 pub fn get_comment(&self) -> Option<String> {
518 self.inner
519 .lock()
520 .unwrap_or_else(|e| e.into_inner())
521 .comment
522 .clone()
523 }
524
525 pub fn mark_as_new(&self, key: EntityKey) {
527 self.inner
528 .lock()
529 .unwrap_or_else(|e| e.into_inner())
530 .new_keys
531 .insert(key);
532 }
533
534 pub fn is_new(&self, key: &EntityKey) -> bool {
536 self.inner
537 .lock()
538 .unwrap_or_else(|e| e.into_inner())
539 .new_keys
540 .contains(key)
541 }
542
543 pub fn set_original_snapshot(&self, snapshot: EntitySnapshot) {
545 self.inner
546 .lock()
547 .unwrap_or_else(|e| e.into_inner())
548 .original_snapshot = Some(OriginalSnapshot::Materialized(snapshot));
549 }
550
551 pub fn set_original_compact_row(&self, row: teaql_core::CompactRow) {
553 self.inner
554 .lock()
555 .unwrap_or_else(|e| e.into_inner())
556 .original_snapshot = Some(OriginalSnapshot::Compact(row));
557 }
558
559 pub fn original_snapshot(&self) -> Option<EntitySnapshot> {
561 self.inner
562 .lock()
563 .unwrap_or_else(|e| e.into_inner())
564 .original_snapshot
565 .as_ref()
566 .map(|snapshot| match snapshot {
567 OriginalSnapshot::Materialized(snapshot) => snapshot.clone(),
568 OriginalSnapshot::Compact(row) => EntitySnapshot::from(row.clone().into_map()),
569 })
570 }
571
572 pub fn mark_as_delete(&self, key: EntityKey) {
577 let mut context = self.inner.lock().unwrap_or_else(|e| e.into_inner());
578 context.change_sets.clear_entity(&key);
579 context.deleted_keys.insert(key);
580 }
581
582 pub fn is_marked_as_delete(&self, key: &EntityKey) -> bool {
584 self.inner
585 .lock()
586 .unwrap_or_else(|e| e.into_inner())
587 .deleted_keys
588 .contains(key)
589 }
590
591 pub fn changed_field_names(&self, key: &EntityKey) -> BTreeSet<String> {
594 self.inner
595 .lock()
596 .unwrap_or_else(|e| e.into_inner())
597 .change_sets
598 .changed_field_names(key)
599 }
600 pub fn deleted_keys(&self) -> std::collections::BTreeSet<EntityKey> {
601 self.inner
602 .lock()
603 .unwrap_or_else(|e| e.into_inner())
604 .deleted_keys
605 .clone()
606 }
607
608 pub fn new_keys(&self) -> std::collections::BTreeSet<EntityKey> {
609 self.inner
610 .lock()
611 .unwrap_or_else(|e| e.into_inner())
612 .new_keys
613 .clone()
614 }
615
616 pub fn get_original_version(&self, key: &EntityKey) -> Option<i64> {
617 self.inner
618 .lock()
619 .unwrap_or_else(|e| e.into_inner())
620 .original_versions
621 .get(key)
622 }
623
624 pub fn get_trace_chain(&self, key: &EntityKey) -> Vec<teaql_core::TraceNode> {
625 self.inner
626 .lock()
627 .unwrap_or_else(|e| e.into_inner())
628 .trace_chains
629 .get(key)
630 .cloned()
631 .unwrap_or_default()
632 }
633
634 pub fn set_original_version(&self, key: EntityKey, version: i64) {
635 self.inner
636 .lock()
637 .unwrap_or_else(|e| e.into_inner())
638 .original_versions
639 .insert(key, version);
640 }
641}
642
643pub trait LedgerEntity: teaql_core::Entity {
644 fn entity_root(&self) -> Option<EntityRoot>;
645}