Skip to main content

icydb_core/db/registry/
registry.rs

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