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