1pub mod backend;
5pub mod backup;
6pub mod lease;
7pub mod metrics;
8pub mod node;
9pub mod server;
10#[cfg(feature = "cljrs")]
11pub mod txfn;
12
13pub use backend::{LogBackend, NodeStore, StorageConnectionError, StoreSpec};
14
15use corium_core::{EntityId, IndexOrder, KeywordInterner, Partition, Schema};
16use corium_db::{Db, FIRST_USER_ID, Idents};
17use corium_index::Segment;
18use corium_log::{LogError, TransactionLog, TxRecord};
19use corium_store::{BlobStore, RootStore, StoreError};
20use corium_tx::{PreparedTx, TxError, TxItem, prepare};
21use std::{
22 sync::{Arc, Mutex, mpsc},
23 time::{SystemTime, UNIX_EPOCH},
24};
25use thiserror::Error;
26
27#[derive(Clone, Debug)]
29pub struct TxReport {
30 pub db_before: Db,
32 pub db_after: Db,
34 pub tx: PreparedTx,
36 pub tx_instant: i64,
38}
39
40#[derive(Debug, Error)]
42pub enum TransactError {
43 #[error(transparent)]
45 Tx(#[from] TxError),
46 #[error(transparent)]
48 Log(#[from] LogError),
49 #[error(transparent)]
51 Store(#[from] StoreError),
52 #[error("system clock is before Unix epoch")]
54 Clock,
55 #[error("index task failed: {0}")]
57 IndexTask(String),
58 #[error("an asynchronous transaction is already in progress")]
60 AsyncTransactionPending,
61 #[error("deposed: database root is owned by lease version {published}")]
63 Deposed {
64 published: u64,
66 },
67}
68
69struct State {
70 db: Db,
71 next_user: u64,
72 last_instant: i64,
73 subscribers: Vec<mpsc::Sender<TxReport>>,
74 async_pending: bool,
75}
76
77struct AsyncPending<'a> {
78 state: &'a Mutex<State>,
79 active: bool,
80}
81
82impl Drop for AsyncPending<'_> {
83 fn drop(&mut self) {
84 if self.active {
85 self.state
86 .lock()
87 .unwrap_or_else(std::sync::PoisonError::into_inner)
88 .async_pending = false;
89 }
90 }
91}
92
93fn next_user_id<'a>(datoms: impl Iterator<Item = &'a corium_core::Datom>, floor: u64) -> u64 {
96 datoms
97 .filter(|d| d.e.partition() == Partition::User as u32)
98 .map(|d| d.e.sequence() + 1)
99 .fold(floor, u64::max)
100}
101
102pub struct Prepared {
106 pub record: TxRecord,
108 db_before: Db,
109 db_after: Db,
110 tx: PreparedTx,
111 tx_instant: i64,
112}
113
114pub struct BatchCursor {
123 db: Db,
124 next_user: u64,
125 last_instant: i64,
126}
127
128impl BatchCursor {
129 #[must_use]
132 pub fn db(&self) -> &Db {
133 &self.db
134 }
135
136 pub fn prepare(
145 &mut self,
146 items: impl IntoIterator<Item = TxItem>,
147 now_ms: i64,
148 ) -> Result<Prepared, TxError> {
149 let before = self.db.clone();
150 let t = before.basis_t() + 1;
151 let tx_id = EntityId::new(Partition::Tx as u32, t);
152 let prepared = prepare(&before, items, tx_id, self.next_user)?;
153 let tx_instant = now_ms.max(self.last_instant.saturating_add(1));
154 let after = before.clone().with_transaction(t, &prepared.datoms);
155 self.next_user = prepared
156 .tempids
157 .values()
158 .filter(|e| e.partition() == Partition::User as u32)
159 .map(|e| e.sequence() + 1)
160 .max()
161 .unwrap_or(self.next_user)
162 .max(self.next_user);
163 self.last_instant = tx_instant;
164 self.db = after.clone();
165 Ok(Prepared {
166 record: TxRecord {
167 t,
168 tx_instant,
169 datoms: prepared.datoms.clone(),
170 },
171 db_before: before,
172 db_after: after,
173 tx: prepared,
174 tx_instant,
175 })
176 }
177}
178
179pub struct EmbeddedTransactor {
181 log: Arc<dyn TransactionLog>,
182 state: Mutex<State>,
183 async_commit: tokio::sync::Mutex<()>,
184}
185impl EmbeddedTransactor {
186 pub fn recover(schema: Schema, log: Arc<dyn TransactionLog>) -> Result<Self, TransactError> {
191 Self::recover_from(Db::new(schema), log)
192 }
193
194 pub fn recover_from(base: Db, log: Arc<dyn TransactionLog>) -> Result<Self, TransactError> {
200 let mut db = base;
201 let mut last_instant = i64::MIN;
202 for record in log.replay()? {
203 db = db.with_transaction(record.t, &record.datoms);
204 last_instant = last_instant.max(record.tx_instant);
205 }
206 let next_user = next_user_id(db.recorded_datoms(), FIRST_USER_ID);
210 Ok(Self {
211 log,
212 state: Mutex::new(State {
213 db,
214 next_user,
215 last_instant,
216 subscribers: Vec::new(),
217 async_pending: false,
218 }),
219 async_commit: tokio::sync::Mutex::new(()),
220 })
221 }
222
223 pub async fn recover_from_async(
228 base: Db,
229 log: Arc<dyn TransactionLog>,
230 ) -> Result<Self, TransactError> {
231 let records = log.replay_async().await?;
232 Ok(Self::recover_from_records(base, log, records))
233 }
234
235 fn recover_from_records(
236 mut db: Db,
237 log: Arc<dyn TransactionLog>,
238 records: Vec<TxRecord>,
239 ) -> Self {
240 let mut last_instant = i64::MIN;
241 for record in records {
242 db = db.with_transaction(record.t, &record.datoms);
243 last_instant = last_instant.max(record.tx_instant);
244 }
245 let next_user = next_user_id(db.recorded_datoms(), FIRST_USER_ID);
246 Self {
247 log,
248 state: Mutex::new(State {
249 db,
250 next_user,
251 last_instant,
252 subscribers: Vec::new(),
253 async_pending: false,
254 }),
255 async_commit: tokio::sync::Mutex::new(()),
256 }
257 }
258
259 pub fn recover_from_snapshot(
284 snapshot: Db,
285 next_entity_id: u64,
286 last_tx_instant: i64,
287 log: Arc<dyn TransactionLog>,
288 ) -> Result<Self, TransactError> {
289 let mut db = snapshot;
290 let index_basis = db.basis_t();
291 let mut last_instant = last_tx_instant;
292 let mut next_user = next_entity_id.max(FIRST_USER_ID);
295 for record in log.tx_range(index_basis + 1, None)? {
296 db = db.with_transaction(record.t, &record.datoms);
297 last_instant = last_instant.max(record.tx_instant);
298 next_user = next_user.max(next_user_id(record.datoms.iter(), next_user));
299 }
300 Ok(Self {
301 log,
302 state: Mutex::new(State {
303 db,
304 next_user,
305 last_instant,
306 subscribers: Vec::new(),
307 async_pending: false,
308 }),
309 async_commit: tokio::sync::Mutex::new(()),
310 })
311 }
312
313 pub async fn recover_from_snapshot_async(
319 snapshot: Db,
320 next_entity_id: u64,
321 last_tx_instant: i64,
322 log: Arc<dyn TransactionLog>,
323 ) -> Result<Self, TransactError> {
324 let index_basis = snapshot.basis_t();
325 let records = log.tx_range_async(index_basis + 1, None).await?;
326 let mut db = snapshot;
327 let mut last_instant = last_tx_instant;
328 let mut next_user = next_entity_id.max(FIRST_USER_ID);
329 for record in records {
330 db = db.with_transaction(record.t, &record.datoms);
331 last_instant = last_instant.max(record.tx_instant);
332 next_user = next_user.max(next_user_id(record.datoms.iter(), next_user));
333 }
334 Ok(Self {
335 log,
336 state: Mutex::new(State {
337 db,
338 next_user,
339 last_instant,
340 subscribers: Vec::new(),
341 async_pending: false,
342 }),
343 async_commit: tokio::sync::Mutex::new(()),
344 })
345 }
346
347 fn recovery_snapshot(&self) -> (Db, u64, i64) {
351 let state = self
352 .state
353 .lock()
354 .unwrap_or_else(std::sync::PoisonError::into_inner);
355 (state.db.clone(), state.next_user, state.last_instant)
356 }
357 #[must_use]
359 pub fn db(&self) -> Db {
360 self.state
361 .lock()
362 .unwrap_or_else(std::sync::PoisonError::into_inner)
363 .db
364 .clone()
365 }
366 pub fn subscribe(&self) -> mpsc::Receiver<TxReport> {
368 let (tx, rx) = mpsc::channel();
369 self.state
370 .lock()
371 .unwrap_or_else(std::sync::PoisonError::into_inner)
372 .subscribers
373 .push(tx);
374 rx
375 }
376 pub fn transact(
382 &self,
383 items: impl IntoIterator<Item = TxItem>,
384 ) -> Result<TxReport, TransactError> {
385 let mut state = self
386 .state
387 .lock()
388 .unwrap_or_else(std::sync::PoisonError::into_inner);
389 if state.async_pending {
390 return Err(TransactError::AsyncTransactionPending);
391 }
392 let before = state.db.clone();
393 let t = before.basis_t() + 1;
394 let tx_id = EntityId::new(Partition::Tx as u32, t);
395 let prepared = prepare(&before, items, tx_id, state.next_user)?;
396 let millis = i64::try_from(
397 SystemTime::now()
398 .duration_since(UNIX_EPOCH)
399 .map_err(|_| TransactError::Clock)?
400 .as_millis(),
401 )
402 .unwrap_or(i64::MAX);
403 let tx_instant = millis.max(state.last_instant.saturating_add(1));
404 self.log.append(&TxRecord {
405 t,
406 tx_instant,
407 datoms: prepared.datoms.clone(),
408 })?;
409 state.db = before.with_transaction(t, &prepared.datoms);
410 state.last_instant = tx_instant;
411 state.next_user = prepared
412 .tempids
413 .values()
414 .filter(|e| e.partition() == Partition::User as u32)
415 .map(|e| e.sequence() + 1)
416 .max()
417 .unwrap_or(state.next_user)
418 .max(state.next_user);
419 let report = TxReport {
420 db_before: before,
421 db_after: state.db.clone(),
422 tx: prepared,
423 tx_instant,
424 };
425 state
426 .subscribers
427 .retain(|subscriber| subscriber.send(report.clone()).is_ok());
428 Ok(report)
429 }
430
431 pub async fn transact_async(
440 &self,
441 items: impl IntoIterator<Item = TxItem>,
442 ) -> Result<TxReport, TransactError> {
443 let _commit = self.async_commit.lock().await;
444 let (before, prepared, t, tx_instant) = {
445 let mut state = self
446 .state
447 .lock()
448 .unwrap_or_else(std::sync::PoisonError::into_inner);
449 if state.async_pending {
450 return Err(TransactError::AsyncTransactionPending);
451 }
452 let before = state.db.clone();
453 let t = before.basis_t() + 1;
454 let tx_id = EntityId::new(Partition::Tx as u32, t);
455 let prepared = prepare(&before, items, tx_id, state.next_user)?;
456 let millis = i64::try_from(
457 SystemTime::now()
458 .duration_since(UNIX_EPOCH)
459 .map_err(|_| TransactError::Clock)?
460 .as_millis(),
461 )
462 .unwrap_or(i64::MAX);
463 let tx_instant = millis.max(state.last_instant.saturating_add(1));
464 state.async_pending = true;
465 (before, prepared, t, tx_instant)
466 };
467 let record = TxRecord {
468 t,
469 tx_instant,
470 datoms: prepared.datoms.clone(),
471 };
472 let mut pending = AsyncPending {
473 state: &self.state,
474 active: true,
475 };
476 self.log.append_async(&record).await?;
477 let mut state = self
478 .state
479 .lock()
480 .unwrap_or_else(std::sync::PoisonError::into_inner);
481 debug_assert_eq!(state.db.basis_t() + 1, t);
482 state.db = state.db.clone().with_transaction(t, &prepared.datoms);
486 state.last_instant = tx_instant;
487 state.next_user = prepared
488 .tempids
489 .values()
490 .filter(|e| e.partition() == Partition::User as u32)
491 .map(|e| e.sequence() + 1)
492 .max()
493 .unwrap_or(state.next_user)
494 .max(state.next_user);
495 state.async_pending = false;
496 pending.active = false;
497 let report = TxReport {
498 db_before: before,
499 db_after: state.db.clone(),
500 tx: prepared,
501 tx_instant,
502 };
503 state
504 .subscribers
505 .retain(|subscriber| subscriber.send(report.clone()).is_ok());
506 Ok(report)
507 }
508 #[must_use]
512 pub fn batch_cursor(&self) -> BatchCursor {
513 let state = self
514 .state
515 .lock()
516 .unwrap_or_else(std::sync::PoisonError::into_inner);
517 BatchCursor {
518 db: state.db.clone(),
519 next_user: state.next_user,
520 last_instant: state.last_instant,
521 }
522 }
523
524 pub fn install_batch(&self, cursor: BatchCursor, prepared: Vec<Prepared>) -> Vec<TxReport> {
532 let mut state = self
533 .state
534 .lock()
535 .unwrap_or_else(std::sync::PoisonError::into_inner);
536 state.db = cursor.db;
537 state.next_user = state.next_user.max(cursor.next_user);
538 state.last_instant = state.last_instant.max(cursor.last_instant);
539 let reports: Vec<TxReport> = prepared
540 .into_iter()
541 .map(|p| TxReport {
542 db_before: p.db_before,
543 db_after: p.db_after,
544 tx: p.tx,
545 tx_instant: p.tx_instant,
546 })
547 .collect();
548 for report in &reports {
549 state
550 .subscribers
551 .retain(|subscriber| subscriber.send(report.clone()).is_ok());
552 }
553 reports
554 }
555
556 pub fn update_naming(&self, idents: Idents, interner: KeywordInterner) {
559 let mut state = self
560 .state
561 .lock()
562 .unwrap_or_else(std::sync::PoisonError::into_inner);
563 state.db = state.db.clone().with_naming(idents, interner);
564 }
565
566 pub async fn publish_indexes(
587 &self,
588 store: &(impl BlobStore + RootStore),
589 root_name: &str,
590 lease_version: u64,
591 ) -> Result<DbRoot, TransactError> {
592 let (snapshot, next_entity_id, last_tx_instant) = self.recovery_snapshot();
593 let datoms = snapshot.datoms();
594 let chunked = tokio::task::spawn_blocking(move || {
595 [
596 IndexOrder::Eavt,
597 IndexOrder::Aevt,
598 IndexOrder::Avet,
599 IndexOrder::Vaet,
600 ]
601 .into_iter()
602 .map(|order| {
603 let segment = Segment::build(order, datoms.clone());
604 corium_store::chunk_segment_keys(segment.entries().map(|(key, _)| key.as_slice()))
605 })
606 .collect::<Vec<_>>()
607 })
608 .await
609 .map_err(|error| TransactError::IndexTask(error.to_string()))?;
610 let mut ids = Vec::new();
611 for chunks in chunked {
612 let mut children = Vec::new();
613 for chunk in chunks {
614 children.push(store.put_if_absent(&chunk).await?);
615 }
616 let manifest = corium_store::encode_index_manifest(&children);
617 ids.push(store.put_if_absent(&manifest).await?);
618 }
619 let root = DbRoot {
620 format_version: corium_store::FORMAT_VERSION,
621 lease_version,
622 owner: String::new(),
623 lease_expires_unix_ms: 0,
624 owner_endpoint: String::new(),
625 index_basis_t: snapshot.basis_t(),
626 roots: Some([
627 ids[0].clone(),
628 ids[1].clone(),
629 ids[2].clone(),
630 ids[3].clone(),
631 ]),
632 next_entity_id,
634 last_tx_instant,
635 };
636 publish_root(store, root_name, &root).await?;
637 Ok(root)
638 }
639}
640
641pub async fn publish_root(
648 store: &dyn RootStore,
649 root_name: &str,
650 root: &DbRoot,
651) -> Result<(), TransactError> {
652 loop {
653 let previous = store.get_root(root_name).await?;
654 let stored = previous.as_deref().and_then(DbRoot::decode);
655 let mut next = root.clone();
656 if let Some(stored) = stored {
657 if stored.lease_version > root.lease_version {
658 return Err(TransactError::Deposed {
659 published: stored.lease_version,
660 });
661 }
662 if stored.lease_version == root.lease_version
663 && stored.index_basis_t >= root.index_basis_t
664 {
665 return Ok(());
666 }
667 if stored.lease_version == root.lease_version {
670 next.owner = stored.owner;
671 next.lease_expires_unix_ms = stored.lease_expires_unix_ms;
672 next.owner_endpoint = stored.owner_endpoint;
673 }
674 }
675 match store
676 .cas_root(root_name, previous.as_deref(), &next.encode())
677 .await
678 {
679 Ok(()) => return Ok(()),
680 Err(StoreError::CasFailed { .. }) => {}
681 Err(error) => return Err(error.into()),
682 }
683 }
684}
685
686pub use corium_store::{DbRoot, db_root_name};