kmp_adapter_embedded/adapter/
store.rs1use std::fs;
2use std::path::Path;
3use std::sync::Arc;
4
5use kmp_domain::PortError;
6
7use super::engine::{Engine, ReadTx, Table, WriteTx};
8use super::format_version::{self, StorageEngine};
9
10#[derive(Debug, Clone)]
20pub struct EmbeddedKernelStore {
21 engine: Arc<dyn Engine>,
22}
23
24impl EmbeddedKernelStore {
25 pub fn open(data_dir: &Path) -> Result<Self, PortError> {
30 Self::open_as(data_dir, None)
31 }
32
33 pub fn open_with_engine(data_dir: &Path, engine: StorageEngine) -> Result<Self, PortError> {
37 Self::open_as(data_dir, Some(engine))
38 }
39
40 pub fn engine_of(data_dir: &Path) -> Result<StorageEngine, PortError> {
42 format_version::check_or_stamp_as(data_dir, None)
43 }
44
45 fn open_as(data_dir: &Path, wanted: Option<StorageEngine>) -> Result<Self, PortError> {
46 fs::create_dir_all(data_dir).map_err(|error| {
47 PortError::Unavailable(format!(
48 "embedded store could not create data dir `{}`: {error}",
49 data_dir.display()
50 ))
51 })?;
52 let engine = format_version::check_or_stamp_as(data_dir, wanted)?;
53
54 let store_file = format_version::store_file_path_for(data_dir, engine);
55 fs::create_dir_all(store_file.parent().expect("store file has a parent")).map_err(
56 |error| {
57 PortError::Unavailable(format!(
58 "embedded store could not create store dir under `{}`: {error}",
59 data_dir.display()
60 ))
61 },
62 )?;
63
64 let engine: Arc<dyn Engine> =
65 Arc::new(super::engine::sqlite::SqliteEngine::open_file(&store_file)?);
66 let store = Self { engine };
67 super::card_history_format::upgrade_stamp(data_dir)?;
68 super::node_card_adoption::adopt(&store)?;
69 Ok(store)
70 }
71
72 pub async fn read_snapshot(&self) -> Result<Self, PortError> {
75 self.run(Self::pin_snapshot).await
76 }
77
78 pub(crate) fn pin_snapshot(&self) -> Result<Self, PortError> {
79 Ok(Self {
80 engine: self.engine.read_snapshot()?,
81 })
82 }
83
84 pub(crate) fn read_revision(&self) -> Option<kmp_domain::GraphReadRevision> {
85 self.engine.graph_read_revision()
86 }
87
88 pub(crate) fn begin_write(&self) -> Result<Box<dyn WriteTx + '_>, PortError> {
89 self.engine.begin_write()
90 }
91
92 pub(crate) fn begin_read(&self) -> Result<Box<dyn ReadTx + '_>, PortError> {
93 self.engine.begin_read()
94 }
95
96 pub(crate) async fn run<T, F>(&self, task: F) -> Result<T, PortError>
99 where
100 T: Send + 'static,
101 F: FnOnce(&EmbeddedKernelStore) -> Result<T, PortError> + Send + 'static,
102 {
103 let store = self.clone();
104 tokio::task::spawn_blocking(move || task(&store))
105 .await
106 .map_err(|error| {
107 PortError::Unavailable(format!("embedded store worker failed: {error}"))
108 })?
109 }
110
111 pub async fn event_log_stats(&self) -> Result<(u64, u64), PortError> {
114 self.run(|store| {
115 let tx = store.begin_read()?;
116 let count = tx.count(Table::EventLog)?;
117 let last_sequence = tx.last_u64(Table::EventLog)?.map_or(0, |(key, _)| key);
118 Ok((count, last_sequence))
119 })
120 .await
121 }
122
123 pub fn compact_data_dir(data_dir: &Path) -> Result<bool, PortError> {
128 let engine = format_version::check_or_stamp(data_dir)?;
129 let store_file = format_version::store_file_path_for(data_dir, engine);
130 super::engine::sqlite::SqliteEngine::compact_file(&store_file)
131 }
132}
133
134pub(crate) fn aggregate_key(root_node_id: &str, role: &str) -> String {
135 format!("{root_node_id}\u{1f}{role}")
136}