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