1pub mod lease;
5pub mod node;
6pub mod server;
7
8use corium_core::{EntityId, IndexOrder, KeywordInterner, Partition, Schema};
9use corium_db::{Db, FIRST_USER_ID, Idents};
10use corium_index::Segment;
11use corium_log::{LogError, TransactionLog, TxRecord};
12use corium_store::{BlobStore, RootStore, StoreError};
13use corium_tx::{PreparedTx, TxError, TxItem, prepare};
14use std::{
15 sync::{Arc, Mutex, mpsc},
16 time::{SystemTime, UNIX_EPOCH},
17};
18use thiserror::Error;
19
20#[derive(Clone, Debug)]
22pub struct TxReport {
23 pub db_before: Db,
25 pub db_after: Db,
27 pub tx: PreparedTx,
29 pub tx_instant: i64,
31}
32
33#[derive(Debug, Error)]
35pub enum TransactError {
36 #[error(transparent)]
38 Tx(#[from] TxError),
39 #[error(transparent)]
41 Log(#[from] LogError),
42 #[error(transparent)]
44 Store(#[from] StoreError),
45 #[error("system clock is before Unix epoch")]
47 Clock,
48 #[error("deposed: database root is owned by lease version {published}")]
50 Deposed {
51 published: u64,
53 },
54}
55
56struct State {
57 db: Db,
58 next_user: u64,
59 last_instant: i64,
60 subscribers: Vec<mpsc::Sender<TxReport>>,
61}
62
63pub struct EmbeddedTransactor {
65 log: Arc<dyn TransactionLog>,
66 state: Mutex<State>,
67}
68impl EmbeddedTransactor {
69 pub fn recover(schema: Schema, log: Arc<dyn TransactionLog>) -> Result<Self, TransactError> {
74 Self::recover_from(Db::new(schema), log)
75 }
76
77 pub fn recover_from(base: Db, log: Arc<dyn TransactionLog>) -> Result<Self, TransactError> {
83 let mut db = base;
84 let mut last_instant = i64::MIN;
85 for record in log.replay()? {
86 db = db.with_transaction(record.t, &record.datoms);
87 last_instant = last_instant.max(record.tx_instant);
88 }
89 let next_user = db
93 .recorded_datoms()
94 .iter()
95 .filter(|d| d.e.partition() == Partition::User as u32)
96 .map(|d| d.e.sequence() + 1)
97 .max()
98 .unwrap_or(FIRST_USER_ID);
99 Ok(Self {
100 log,
101 state: Mutex::new(State {
102 db,
103 next_user,
104 last_instant,
105 subscribers: Vec::new(),
106 }),
107 })
108 }
109 #[must_use]
111 pub fn db(&self) -> Db {
112 self.state
113 .lock()
114 .unwrap_or_else(std::sync::PoisonError::into_inner)
115 .db
116 .clone()
117 }
118 pub fn subscribe(&self) -> mpsc::Receiver<TxReport> {
120 let (tx, rx) = mpsc::channel();
121 self.state
122 .lock()
123 .unwrap_or_else(std::sync::PoisonError::into_inner)
124 .subscribers
125 .push(tx);
126 rx
127 }
128 pub fn transact(
134 &self,
135 items: impl IntoIterator<Item = TxItem>,
136 ) -> Result<TxReport, TransactError> {
137 let mut state = self
138 .state
139 .lock()
140 .unwrap_or_else(std::sync::PoisonError::into_inner);
141 let before = state.db.clone();
142 let t = before.basis_t() + 1;
143 let tx_id = EntityId::new(Partition::Tx as u32, t);
144 let prepared = prepare(&before, items, tx_id, state.next_user)?;
145 let millis = i64::try_from(
146 SystemTime::now()
147 .duration_since(UNIX_EPOCH)
148 .map_err(|_| TransactError::Clock)?
149 .as_millis(),
150 )
151 .unwrap_or(i64::MAX);
152 let tx_instant = millis.max(state.last_instant.saturating_add(1));
153 self.log.append(&TxRecord {
154 t,
155 tx_instant,
156 datoms: prepared.datoms.clone(),
157 })?;
158 state.db = before.with_transaction(t, &prepared.datoms);
159 state.last_instant = tx_instant;
160 state.next_user = prepared
161 .tempids
162 .values()
163 .filter(|e| e.partition() == Partition::User as u32)
164 .map(|e| e.sequence() + 1)
165 .max()
166 .unwrap_or(state.next_user)
167 .max(state.next_user);
168 let report = TxReport {
169 db_before: before,
170 db_after: state.db.clone(),
171 tx: prepared,
172 tx_instant,
173 };
174 state
175 .subscribers
176 .retain(|subscriber| subscriber.send(report.clone()).is_ok());
177 Ok(report)
178 }
179 pub fn update_naming(&self, idents: Idents, interner: KeywordInterner) {
182 let mut state = self
183 .state
184 .lock()
185 .unwrap_or_else(std::sync::PoisonError::into_inner);
186 state.db = state.db.clone().with_naming(idents, interner);
187 }
188
189 pub fn publish_indexes(
204 &self,
205 store: &(impl BlobStore + RootStore),
206 root_name: &str,
207 lease_version: u64,
208 ) -> Result<DbRoot, TransactError> {
209 let snapshot = self.db();
210 let datoms = snapshot.datoms();
211 let mut ids = Vec::new();
212 for order in [
213 IndexOrder::Eavt,
214 IndexOrder::Aevt,
215 IndexOrder::Avet,
216 IndexOrder::Vaet,
217 ] {
218 let segment = Segment::build(order, datoms.clone());
219 let mut bytes = Vec::new();
220 for (key, _) in segment.entries() {
221 bytes.extend_from_slice(&(key.len() as u64).to_be_bytes());
222 bytes.extend_from_slice(key);
223 }
224 ids.push(store.put(&bytes)?);
225 }
226 let root = DbRoot {
227 lease_version,
228 index_basis_t: snapshot.basis_t(),
229 roots: Some([
230 ids[0].clone(),
231 ids[1].clone(),
232 ids[2].clone(),
233 ids[3].clone(),
234 ]),
235 };
236 publish_root(store, root_name, &root)?;
237 Ok(root)
238 }
239}
240
241pub fn publish_root(
248 store: &dyn RootStore,
249 root_name: &str,
250 root: &DbRoot,
251) -> Result<(), TransactError> {
252 let encoded = root.encode();
253 loop {
254 let previous = store.get_root(root_name)?;
255 if let Some(stored) = previous.as_deref().and_then(DbRoot::decode) {
256 if stored.lease_version > root.lease_version {
257 return Err(TransactError::Deposed {
258 published: stored.lease_version,
259 });
260 }
261 if stored.lease_version == root.lease_version
262 && stored.index_basis_t >= root.index_basis_t
263 {
264 return Ok(());
265 }
266 }
267 match store.cas_root(root_name, previous.as_deref(), &encoded) {
268 Ok(()) => return Ok(()),
269 Err(StoreError::CasFailed { .. }) => {}
270 Err(error) => return Err(error.into()),
271 }
272 }
273}
274
275pub use corium_store::{DbRoot, db_root_name};