bitemporal_runtime/
lib.rs1mod error;
23mod queries;
24#[cfg(feature = "schema")]
25pub mod schema;
26#[cfg(feature = "sqlite")]
27mod sqlite;
28mod types;
29
30pub use error::BitemporalError;
31pub use queries::{append_supersede, as_of_query, temporal_snapshot};
32#[cfg(feature = "sqlite")]
33pub use sqlite::SqliteDb;
34pub use types::{BitemporalRecord, RecordId, SupersessionReceipt, SupersessionTarget};
35
36use chrono::{DateTime, Utc};
37use std::collections::BTreeMap;
38
39#[derive(Debug, Clone)]
41pub struct InMemoryDb {
42 records: BTreeMap<String, Vec<types::BitemporalRecord>>,
44}
45
46impl InMemoryDb {
47 pub fn new() -> Self {
49 Self {
50 records: BTreeMap::new(),
51 }
52 }
53
54 pub fn insert(&mut self, record: types::BitemporalRecord) {
56 let id = record.id.clone();
57 self.records.entry(id).or_default().push(record);
58 }
59
60 pub fn get_versions(&self, id: &str) -> Option<&Vec<types::BitemporalRecord>> {
62 self.records.get(id)
63 }
64
65 pub fn snapshot_at(&self, recorded_time: DateTime<Utc>) -> Vec<types::BitemporalRecord> {
67 let mut result = Vec::new();
68 for versions in self.records.values() {
69 let mut best: Option<&types::BitemporalRecord> = None;
70 for v in versions {
71 if v.recorded_time <= recorded_time
72 && best
73 .map(|b| v.recorded_time > b.recorded_time)
74 .unwrap_or(true)
75 {
76 best = Some(v);
77 }
78 }
79 if let Some(r) = best {
80 result.push(r.clone());
81 }
82 }
83 result
84 }
85
86 pub fn as_of(
88 &self,
89 valid_time: DateTime<Utc>,
90 recorded_time: DateTime<Utc>,
91 ) -> Vec<types::BitemporalRecord> {
92 let mut result = Vec::new();
93 for versions in self.records.values() {
94 let mut best: Option<&types::BitemporalRecord> = None;
95 for v in versions {
96 if v.recorded_time <= recorded_time
97 && v.valid_time <= valid_time
98 && best
99 .map(|b| v.recorded_time > b.recorded_time)
100 .unwrap_or(true)
101 {
102 best = Some(v);
103 }
104 }
105 if let Some(r) = best {
106 result.push(r.clone());
107 }
108 }
109 result
110 }
111
112 pub fn len(&self) -> usize {
114 self.records.len()
115 }
116
117 pub fn is_empty(&self) -> bool {
119 self.records.is_empty()
120 }
121}
122
123impl Default for InMemoryDb {
124 fn default() -> Self {
125 Self::new()
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn test_in_memory_basic_insert() {
135 let mut db = InMemoryDb::new();
136 let record = types::BitemporalRecord {
137 id: "ep1".to_string(),
138 valid_time: Utc::now(),
139 recorded_time: Utc::now(),
140 value: (),
141 };
142 db.insert(record);
143 let versions = db.get_versions("ep1").unwrap();
144 assert_eq!(versions.len(), 1);
145 }
146}