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