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(
65                StoreRegistryError::StoreAllocationCapabilityMismatch(name.to_string()).into(),
66            );
67        }
68
69        self.stores.push((
70            name,
71            StoreHandle::new(data, index, schema, allocations, capabilities),
72        ));
73
74        Ok(())
75    }
76
77    /// Register one journaled store with its journal-tail storage handle.
78    #[expect(
79        clippy::too_many_arguments,
80        reason = "generated journaled registration adds one journal-tail handle to the existing store triplet"
81    )]
82    pub fn register_journaled_store(
83        &mut self,
84        name: &'static str,
85        data: &'static LocalKey<RefCell<DataStore>>,
86        index: &'static LocalKey<RefCell<IndexStore>>,
87        schema: &'static LocalKey<RefCell<SchemaStore>>,
88        journal: &'static LocalKey<RefCell<JournalTailStore>>,
89        allocations: StoreAllocationIdentities,
90        capabilities: StoreRuntimeStorageCapabilities,
91    ) -> Result<(), InternalError> {
92        self.validate_register_store_shape(name, data, index, schema, allocations, capabilities)?;
93        if capabilities.storage_mode() != StoreRuntimeStorageMode::Journaled
94            || allocations.journal().is_none()
95        {
96            return Err(
97                StoreRegistryError::StoreAllocationCapabilityMismatch(name.to_string()).into(),
98            );
99        }
100
101        self.stores.push((
102            name,
103            StoreHandle::new_journaled(data, index, schema, journal, allocations, capabilities),
104        ));
105
106        Ok(())
107    }
108
109    /// Look up a store handle by path.
110    pub fn try_get_store(&self, path: &str) -> Result<StoreHandle, InternalError> {
111        self.stores
112            .iter()
113            .find_map(|(existing_path, handle)| (*existing_path == path).then_some(*handle))
114            .ok_or_else(|| StoreRegistryError::StoreNotFound(path.to_string()).into())
115    }
116
117    fn validate_register_store_shape(
118        &self,
119        name: &'static str,
120        data: &'static LocalKey<RefCell<DataStore>>,
121        index: &'static LocalKey<RefCell<IndexStore>>,
122        schema: &'static LocalKey<RefCell<SchemaStore>>,
123        allocations: StoreAllocationIdentities,
124        capabilities: StoreRuntimeStorageCapabilities,
125    ) -> Result<(), InternalError> {
126        if self
127            .stores
128            .iter()
129            .any(|(existing_name, _)| *existing_name == name)
130        {
131            return Err(StoreRegistryError::StoreAlreadyRegistered(name.to_string()).into());
132        }
133
134        // Keep one canonical logical store name per physical row/index/schema
135        // store triplet.
136        if let Some(existing_name) =
137            self.stores
138                .iter()
139                .find_map(|(existing_name, existing_handle)| {
140                    (std::ptr::eq(existing_handle.data_store(), data)
141                        && std::ptr::eq(existing_handle.index_store(), index)
142                        && std::ptr::eq(existing_handle.schema_store(), schema))
143                    .then_some(*existing_name)
144                })
145        {
146            return Err(StoreRegistryError::StoreHandleTripletAlreadyRegistered {
147                name: name.to_string(),
148                existing_name: existing_name.to_string(),
149            }
150            .into());
151        }
152
153        if allocations.allocation_identity_capability() != Some(capabilities.allocation_identity())
154            || !allocations.matches_storage_capabilities(capabilities)
155        {
156            return Err(
157                StoreRegistryError::StoreAllocationCapabilityMismatch(name.to_string()).into(),
158            );
159        }
160
161        Ok(())
162    }
163}