icydb_core/db/store/
mod.rs1mod data;
2mod data_key;
3mod row;
4mod storage_key;
5
6pub use data::*;
7pub use data_key::*;
8pub use row::*;
9pub use storage_key::*;
10
11use crate::{
12 db::index::IndexStore,
13 error::{ErrorClass, ErrorOrigin, InternalError},
14};
15use std::{cell::RefCell, collections::HashMap, thread::LocalKey};
16use thiserror::Error as ThisError;
17
18#[derive(Debug, ThisError)]
23pub enum StoreRegistryError {
24 #[error("store '{0}' not found")]
25 StoreNotFound(String),
26 #[error("store '{0}' already registered")]
27 StoreAlreadyRegistered(String),
28}
29
30impl StoreRegistryError {
31 pub(crate) const fn class(&self) -> ErrorClass {
32 match self {
33 Self::StoreNotFound(_) => ErrorClass::Internal,
34 Self::StoreAlreadyRegistered(_) => ErrorClass::InvariantViolation,
35 }
36 }
37}
38
39impl From<StoreRegistryError> for InternalError {
40 fn from(err: StoreRegistryError) -> Self {
41 Self::new(err.class(), ErrorOrigin::Store, err.to_string())
42 }
43}
44
45#[derive(Clone, Copy, Debug)]
52pub struct StoreHandle {
53 data: &'static LocalKey<RefCell<DataStore>>,
54 index: &'static LocalKey<RefCell<IndexStore>>,
55}
56
57impl StoreHandle {
58 #[must_use]
60 pub const fn new(
61 data: &'static LocalKey<RefCell<DataStore>>,
62 index: &'static LocalKey<RefCell<IndexStore>>,
63 ) -> Self {
64 Self { data, index }
65 }
66
67 pub fn with_data<R>(&self, f: impl FnOnce(&DataStore) -> R) -> R {
69 self.data.with_borrow(f)
70 }
71
72 pub fn with_data_mut<R>(&self, f: impl FnOnce(&mut DataStore) -> R) -> R {
74 self.data.with_borrow_mut(f)
75 }
76
77 pub fn with_index<R>(&self, f: impl FnOnce(&IndexStore) -> R) -> R {
79 self.index.with_borrow(f)
80 }
81
82 pub fn with_index_mut<R>(&self, f: impl FnOnce(&mut IndexStore) -> R) -> R {
84 self.index.with_borrow_mut(f)
85 }
86
87 #[must_use]
89 pub const fn data_store(&self) -> &'static LocalKey<RefCell<DataStore>> {
90 self.data
91 }
92
93 #[must_use]
95 pub const fn index_store(&self) -> &'static LocalKey<RefCell<IndexStore>> {
96 self.index
97 }
98}
99
100#[derive(Default)]
107pub struct StoreRegistry {
108 stores: HashMap<&'static str, StoreHandle>,
109}
110
111impl StoreRegistry {
112 #[must_use]
114 pub fn new() -> Self {
115 Self::default()
116 }
117
118 pub fn iter(&self) -> impl Iterator<Item = (&'static str, StoreHandle)> {
120 self.stores.iter().map(|(k, v)| (*k, *v))
121 }
122
123 pub fn register_store(
125 &mut self,
126 name: &'static str,
127 data: &'static LocalKey<RefCell<DataStore>>,
128 index: &'static LocalKey<RefCell<IndexStore>>,
129 ) -> Result<(), InternalError> {
130 if self.stores.contains_key(name) {
131 return Err(StoreRegistryError::StoreAlreadyRegistered(name.to_string()).into());
132 }
133
134 self.stores.insert(name, StoreHandle::new(data, index));
135 Ok(())
136 }
137
138 pub fn try_get_store(&self, path: &str) -> Result<StoreHandle, InternalError> {
140 self.stores
141 .get(path)
142 .copied()
143 .ok_or_else(|| StoreRegistryError::StoreNotFound(path.to_string()).into())
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use crate::{
150 db::{
151 index::IndexStore,
152 store::{DataStore, StoreRegistry},
153 },
154 error::{ErrorClass, ErrorOrigin},
155 test_support::test_memory,
156 };
157 use std::{cell::RefCell, ptr};
158
159 const STORE_PATH: &str = "store_registry_tests::Store";
160
161 thread_local! {
162 static TEST_DATA_STORE: RefCell<DataStore> = RefCell::new(DataStore::init(test_memory(151)));
163 static TEST_INDEX_STORE: RefCell<IndexStore> =
164 RefCell::new(IndexStore::init(test_memory(152)));
165 }
166
167 fn test_registry() -> StoreRegistry {
168 let mut registry = StoreRegistry::new();
169 registry
170 .register_store(STORE_PATH, &TEST_DATA_STORE, &TEST_INDEX_STORE)
171 .expect("test store registration should succeed");
172 registry
173 }
174
175 #[test]
176 fn register_store_binds_data_and_index_handles() {
177 let registry = test_registry();
178 let handle = registry
179 .try_get_store(STORE_PATH)
180 .expect("registered store path should resolve");
181
182 assert!(
183 ptr::eq(handle.data_store(), &TEST_DATA_STORE),
184 "store handle should expose the registered data store accessor"
185 );
186 assert!(
187 ptr::eq(handle.index_store(), &TEST_INDEX_STORE),
188 "store handle should expose the registered index store accessor"
189 );
190
191 let data_rows = handle.with_data(|store| store.len());
192 let index_rows = handle.with_index(IndexStore::len);
193 assert_eq!(data_rows, 0, "fresh test data store should be empty");
194 assert_eq!(index_rows, 0, "fresh test index store should be empty");
195 }
196
197 #[test]
198 fn missing_store_path_rejected_before_access() {
199 let registry = StoreRegistry::new();
200 let err = registry
201 .try_get_store("store_registry_tests::Missing")
202 .expect_err("missing path should fail lookup");
203
204 assert_eq!(err.class, ErrorClass::Internal);
205 assert_eq!(err.origin, ErrorOrigin::Store);
206 assert!(
207 err.message
208 .contains("store 'store_registry_tests::Missing' not found"),
209 "missing store lookup should include the missing path"
210 );
211 }
212
213 #[test]
214 fn duplicate_store_registration_is_rejected() {
215 let mut registry = StoreRegistry::new();
216 registry
217 .register_store(STORE_PATH, &TEST_DATA_STORE, &TEST_INDEX_STORE)
218 .expect("initial store registration should succeed");
219
220 let err = registry
221 .register_store(STORE_PATH, &TEST_DATA_STORE, &TEST_INDEX_STORE)
222 .expect_err("duplicate registration should fail");
223 assert_eq!(err.class, ErrorClass::InvariantViolation);
224 assert_eq!(err.origin, ErrorOrigin::Store);
225 assert!(
226 err.message
227 .contains("store 'store_registry_tests::Store' already registered"),
228 "duplicate registration should include the conflicting path"
229 );
230 }
231}