1use crate::{
7 db::{
8 cursor::IndexScanContinuationInput,
9 data::DataStore,
10 data::{DataKey, RawRow, StorageKey},
11 direction::Direction,
12 index::{
13 IndexState, IndexStore, RawIndexEntry, RawIndexKey, SealedStructuralIndexEntryReader,
14 SealedStructuralPrimaryRowReader, StructuralIndexEntryReader,
15 StructuralPrimaryRowReader,
16 },
17 },
18 error::{ErrorClass, ErrorOrigin, InternalError},
19 model::index::IndexModel,
20 types::EntityTag,
21};
22use std::{cell::RefCell, ops::Bound, thread::LocalKey};
23use thiserror::Error as ThisError;
24
25#[derive(Debug, ThisError)]
30#[expect(clippy::enum_variant_names)]
31pub enum StoreRegistryError {
32 #[error("store '{0}' not found")]
33 StoreNotFound(String),
34
35 #[error("store '{0}' already registered")]
36 StoreAlreadyRegistered(String),
37
38 #[error(
39 "store '{name}' reuses the same row/index store pair already registered as '{existing_name}'"
40 )]
41 StoreHandlePairAlreadyRegistered { name: String, existing_name: String },
42}
43
44impl StoreRegistryError {
45 pub(crate) const fn class(&self) -> ErrorClass {
46 match self {
47 Self::StoreNotFound(_) => ErrorClass::Internal,
48 Self::StoreAlreadyRegistered(_) | Self::StoreHandlePairAlreadyRegistered { .. } => {
49 ErrorClass::InvariantViolation
50 }
51 }
52 }
53}
54
55impl From<StoreRegistryError> for InternalError {
56 fn from(err: StoreRegistryError) -> Self {
57 Self::classified(err.class(), ErrorOrigin::Store, err.to_string())
58 }
59}
60
61#[derive(Clone, Copy, Debug)]
67pub struct StoreHandle {
68 data: &'static LocalKey<RefCell<DataStore>>,
69 index: &'static LocalKey<RefCell<IndexStore>>,
70}
71
72impl StoreHandle {
73 #[must_use]
75 pub const fn new(
76 data: &'static LocalKey<RefCell<DataStore>>,
77 index: &'static LocalKey<RefCell<IndexStore>>,
78 ) -> Self {
79 Self { data, index }
80 }
81
82 pub fn with_data<R>(&self, f: impl FnOnce(&DataStore) -> R) -> R {
84 self.data.with_borrow(f)
85 }
86
87 pub fn with_data_mut<R>(&self, f: impl FnOnce(&mut DataStore) -> R) -> R {
89 self.data.with_borrow_mut(f)
90 }
91
92 pub fn with_index<R>(&self, f: impl FnOnce(&IndexStore) -> R) -> R {
94 self.index.with_borrow(f)
95 }
96
97 pub fn with_index_mut<R>(&self, f: impl FnOnce(&mut IndexStore) -> R) -> R {
99 self.index.with_borrow_mut(f)
100 }
101
102 #[must_use]
104 pub(in crate::db) fn index_state(&self) -> IndexState {
105 self.with_index(IndexStore::state)
106 }
107
108 pub(in crate::db) fn mark_index_building(&self) {
110 self.with_index_mut(IndexStore::mark_building);
111 }
112
113 pub(in crate::db) fn mark_index_ready(&self) {
115 self.with_index_mut(IndexStore::mark_ready);
116 }
117
118 pub(in crate::db) fn mark_index_dropping(&self) {
120 self.with_index_mut(IndexStore::mark_dropping);
121 }
122
123 #[must_use]
125 pub const fn data_store(&self) -> &'static LocalKey<RefCell<DataStore>> {
126 self.data
127 }
128
129 #[must_use]
131 pub const fn index_store(&self) -> &'static LocalKey<RefCell<IndexStore>> {
132 self.index
133 }
134}
135
136impl StructuralPrimaryRowReader for StoreHandle {
137 fn read_primary_row_structural(&self, key: &DataKey) -> Result<Option<RawRow>, InternalError> {
138 let raw_key = key.to_raw()?;
139
140 Ok(self.with_data(|store| store.get(&raw_key)))
141 }
142}
143
144impl SealedStructuralPrimaryRowReader for StoreHandle {}
145
146impl StructuralIndexEntryReader for StoreHandle {
147 fn read_index_entry_structural(
148 &self,
149 store: &'static LocalKey<RefCell<IndexStore>>,
150 key: &RawIndexKey,
151 ) -> Result<Option<RawIndexEntry>, InternalError> {
152 Ok(store.with_borrow(|index_store| index_store.get(key)))
153 }
154
155 fn read_index_keys_in_raw_range_structural(
156 &self,
157 _entity_path: &'static str,
158 entity_tag: EntityTag,
159 store: &'static LocalKey<RefCell<IndexStore>>,
160 index: &IndexModel,
161 bounds: (&Bound<RawIndexKey>, &Bound<RawIndexKey>),
162 limit: usize,
163 ) -> Result<Vec<StorageKey>, InternalError> {
164 let data_keys = store.with_borrow(|index_store| {
165 index_store.resolve_data_values_in_raw_range_limited(
166 entity_tag,
167 index,
168 bounds,
169 IndexScanContinuationInput::new(None, Direction::Asc),
170 limit,
171 None,
172 )
173 })?;
174
175 let mut out = Vec::with_capacity(data_keys.len());
176 for data_key in data_keys {
177 out.push(data_key.storage_key());
178 }
179
180 Ok(out)
181 }
182}
183
184impl SealedStructuralIndexEntryReader for StoreHandle {}
185
186#[derive(Default)]
192pub struct StoreRegistry {
193 stores: Vec<(&'static str, StoreHandle)>,
194}
195
196impl StoreRegistry {
197 #[must_use]
199 pub fn new() -> Self {
200 Self::default()
201 }
202
203 pub fn iter(&self) -> impl Iterator<Item = (&'static str, StoreHandle)> {
209 self.stores.iter().copied()
210 }
211
212 pub fn register_store(
214 &mut self,
215 name: &'static str,
216 data: &'static LocalKey<RefCell<DataStore>>,
217 index: &'static LocalKey<RefCell<IndexStore>>,
218 ) -> Result<(), InternalError> {
219 if self
220 .stores
221 .iter()
222 .any(|(existing_name, _)| *existing_name == name)
223 {
224 return Err(StoreRegistryError::StoreAlreadyRegistered(name.to_string()).into());
225 }
226
227 if let Some(existing_name) =
229 self.stores
230 .iter()
231 .find_map(|(existing_name, existing_handle)| {
232 (std::ptr::eq(existing_handle.data_store(), data)
233 && std::ptr::eq(existing_handle.index_store(), index))
234 .then_some(*existing_name)
235 })
236 {
237 return Err(StoreRegistryError::StoreHandlePairAlreadyRegistered {
238 name: name.to_string(),
239 existing_name: existing_name.to_string(),
240 }
241 .into());
242 }
243
244 self.stores.push((name, StoreHandle::new(data, index)));
245
246 Ok(())
247 }
248
249 pub fn try_get_store(&self, path: &str) -> Result<StoreHandle, InternalError> {
251 self.stores
252 .iter()
253 .find_map(|(existing_path, handle)| (*existing_path == path).then_some(*handle))
254 .ok_or_else(|| StoreRegistryError::StoreNotFound(path.to_string()).into())
255 }
256}
257
258#[cfg(test)]
263mod tests {
264 use crate::{
265 db::{data::DataStore, index::IndexStore, registry::StoreRegistry},
266 error::{ErrorClass, ErrorOrigin},
267 testing::test_memory,
268 };
269 use std::{cell::RefCell, ptr};
270
271 const STORE_PATH: &str = "store_registry_tests::Store";
272 const ALIAS_STORE_PATH: &str = "store_registry_tests::StoreAlias";
273
274 thread_local! {
275 static TEST_DATA_STORE: RefCell<DataStore> = RefCell::new(DataStore::init(test_memory(151)));
276 static TEST_INDEX_STORE: RefCell<IndexStore> =
277 RefCell::new(IndexStore::init(test_memory(152)));
278 }
279
280 fn test_registry() -> StoreRegistry {
281 let mut registry = StoreRegistry::new();
282 registry
283 .register_store(STORE_PATH, &TEST_DATA_STORE, &TEST_INDEX_STORE)
284 .expect("test store registration should succeed");
285 registry
286 }
287
288 #[test]
289 fn register_store_binds_data_and_index_handles() {
290 let registry = test_registry();
291 let handle = registry
292 .try_get_store(STORE_PATH)
293 .expect("registered store path should resolve");
294
295 assert!(
296 ptr::eq(handle.data_store(), &TEST_DATA_STORE),
297 "store handle should expose the registered data store accessor"
298 );
299 assert!(
300 ptr::eq(handle.index_store(), &TEST_INDEX_STORE),
301 "store handle should expose the registered index store accessor"
302 );
303
304 let data_rows = handle.with_data(|store| store.len());
305 let index_rows = handle.with_index(IndexStore::len);
306 assert_eq!(data_rows, 0, "fresh test data store should be empty");
307 assert_eq!(index_rows, 0, "fresh test index store should be empty");
308 }
309
310 #[test]
311 fn missing_store_path_rejected_before_access() {
312 let registry = StoreRegistry::new();
313 let err = registry
314 .try_get_store("store_registry_tests::Missing")
315 .expect_err("missing path should fail lookup");
316
317 assert_eq!(err.class, ErrorClass::Internal);
318 assert_eq!(err.origin, ErrorOrigin::Store);
319 assert!(
320 err.message
321 .contains("store 'store_registry_tests::Missing' not found"),
322 "missing store lookup should include the missing path"
323 );
324 }
325
326 #[test]
327 fn duplicate_store_registration_is_rejected() {
328 let mut registry = StoreRegistry::new();
329 registry
330 .register_store(STORE_PATH, &TEST_DATA_STORE, &TEST_INDEX_STORE)
331 .expect("initial store registration should succeed");
332
333 let err = registry
334 .register_store(STORE_PATH, &TEST_DATA_STORE, &TEST_INDEX_STORE)
335 .expect_err("duplicate registration should fail");
336 assert_eq!(err.class, ErrorClass::InvariantViolation);
337 assert_eq!(err.origin, ErrorOrigin::Store);
338 assert!(
339 err.message
340 .contains("store 'store_registry_tests::Store' already registered"),
341 "duplicate registration should include the conflicting path"
342 );
343 }
344
345 #[test]
346 fn alias_store_registration_reusing_same_store_pair_is_rejected() {
347 let mut registry = StoreRegistry::new();
348 registry
349 .register_store(STORE_PATH, &TEST_DATA_STORE, &TEST_INDEX_STORE)
350 .expect("initial store registration should succeed");
351
352 let err = registry
353 .register_store(ALIAS_STORE_PATH, &TEST_DATA_STORE, &TEST_INDEX_STORE)
354 .expect_err("alias registration reusing the same store pair should fail");
355 assert_eq!(err.class, ErrorClass::InvariantViolation);
356 assert_eq!(err.origin, ErrorOrigin::Store);
357 assert!(
358 err.message.contains(
359 "store 'store_registry_tests::StoreAlias' reuses the same row/index store pair"
360 ),
361 "alias registration should include conflicting alias path"
362 );
363 assert!(
364 err.message
365 .contains("registered as 'store_registry_tests::Store'"),
366 "alias registration should include original path"
367 );
368 }
369}