Skip to main content

icydb_core/db/registry/
registry.rs

1use crate::{
2    db::{
3        data::DataStore,
4        index::IndexStore,
5        journal::JournalTailStore,
6        registry::{
7            StoreAllocationIdentities, StoreHandle, StoreRegistryError,
8            StoreRuntimeStorageCapabilities, StoreRuntimeStorageMode,
9        },
10        schema::SchemaStore,
11    },
12    error::InternalError,
13};
14use std::{cell::RefCell, thread::LocalKey};
15
16///
17/// StoreRegistry
18///
19/// StoreRegistry owns the generated mapping from schema `Store` paths to their
20/// row, index, and schema store handles.
21/// It validates registration invariants once at generated wiring time and then
22/// serves cheap immutable lookups during runtime operations.
23///
24
25#[derive(Default)]
26pub struct StoreRegistry {
27    stores: Vec<(&'static str, StoreHandle)>,
28}
29
30impl StoreRegistry {
31    /// Create an empty store registry.
32    #[must_use]
33    pub fn new() -> Self {
34        Self::default()
35    }
36
37    /// Iterate registered stores.
38    ///
39    /// Iteration order follows registration order. Semantic result ordering
40    /// must still not depend on this iteration order; callers that need
41    /// deterministic ordering must sort by store path.
42    pub fn iter(&self) -> impl Iterator<Item = (&'static str, StoreHandle)> {
43        self.stores.iter().copied()
44    }
45
46    /// Register a `Store` path to its row/index/schema store triplet with an
47    /// explicit allocation identity decision.
48    ///
49    /// Generated stable-store wiring supplies stable allocation identities.
50    /// Tests and future non-stable stores must pass
51    /// [`StoreAllocationIdentities::absent`] explicitly when allocation
52    /// identities are intentionally unavailable.
53    pub fn register_store(
54        &mut self,
55        name: &'static str,
56        data: &'static LocalKey<RefCell<DataStore>>,
57        index: &'static LocalKey<RefCell<IndexStore>>,
58        schema: &'static LocalKey<RefCell<SchemaStore>>,
59        allocations: StoreAllocationIdentities,
60        capabilities: StoreRuntimeStorageCapabilities,
61    ) -> Result<(), InternalError> {
62        self.validate_register_store_shape(name, data, index, schema, allocations, capabilities)?;
63        if capabilities.storage_mode() == StoreRuntimeStorageMode::Journaled {
64            return Err(StoreRegistryError::StoreAllocationCapabilityMismatch.into());
65        }
66
67        self.stores.push((
68            name,
69            StoreHandle::new(data, index, schema, allocations, capabilities),
70        ));
71
72        Ok(())
73    }
74
75    /// Register one journaled store with its journal-tail storage handle.
76    #[expect(
77        clippy::too_many_arguments,
78        reason = "generated journaled registration adds one journal-tail handle to the existing store triplet"
79    )]
80    pub fn register_journaled_store(
81        &mut self,
82        name: &'static str,
83        data: &'static LocalKey<RefCell<DataStore>>,
84        index: &'static LocalKey<RefCell<IndexStore>>,
85        schema: &'static LocalKey<RefCell<SchemaStore>>,
86        journal: &'static LocalKey<RefCell<JournalTailStore>>,
87        allocations: StoreAllocationIdentities,
88        capabilities: StoreRuntimeStorageCapabilities,
89    ) -> Result<(), InternalError> {
90        self.validate_register_store_shape(name, data, index, schema, allocations, capabilities)?;
91        if capabilities.storage_mode() != StoreRuntimeStorageMode::Journaled
92            || allocations.journal().is_none()
93        {
94            return Err(StoreRegistryError::StoreAllocationCapabilityMismatch.into());
95        }
96
97        self.stores.push((
98            name,
99            StoreHandle::new_journaled(data, index, schema, journal, allocations, capabilities),
100        ));
101
102        Ok(())
103    }
104
105    /// Look up a store handle by path.
106    pub fn try_get_store(&self, path: &str) -> Result<StoreHandle, InternalError> {
107        self.stores
108            .iter()
109            .find_map(|(existing_path, handle)| (*existing_path == path).then_some(*handle))
110            .ok_or_else(|| StoreRegistryError::StoreNotFound.into())
111    }
112
113    fn validate_register_store_shape(
114        &self,
115        name: &'static str,
116        data: &'static LocalKey<RefCell<DataStore>>,
117        index: &'static LocalKey<RefCell<IndexStore>>,
118        schema: &'static LocalKey<RefCell<SchemaStore>>,
119        allocations: StoreAllocationIdentities,
120        capabilities: StoreRuntimeStorageCapabilities,
121    ) -> Result<(), InternalError> {
122        if self
123            .stores
124            .iter()
125            .any(|(existing_name, _)| *existing_name == name)
126        {
127            return Err(StoreRegistryError::StoreAlreadyRegistered.into());
128        }
129
130        // Keep one canonical logical store name per physical row/index/schema
131        // store triplet.
132        if self.stores.iter().any(|(_, existing_handle)| {
133            std::ptr::eq(existing_handle.data_store(), data)
134                && std::ptr::eq(existing_handle.index_store(), index)
135                && std::ptr::eq(existing_handle.schema_store(), schema)
136        }) {
137            return Err(StoreRegistryError::StoreHandleTripletAlreadyRegistered.into());
138        }
139
140        if allocations.allocation_identity_capability() != Some(capabilities.allocation_identity())
141            || !allocations.matches_storage_capabilities(capabilities)
142        {
143            return Err(StoreRegistryError::StoreAllocationCapabilityMismatch.into());
144        }
145
146        Ok(())
147    }
148}