Skip to main content

icydb_core/db/registry/
registry.rs

1use crate::{
2    db::{
3        data::DataStore,
4        index::IndexStore,
5        registry::{
6            StoreAllocationIdentities, StoreHandle, StoreRegistryError,
7            StoreRuntimeStorageCapabilities,
8        },
9        schema::SchemaStore,
10    },
11    error::InternalError,
12};
13use std::{cell::RefCell, thread::LocalKey};
14
15///
16/// StoreRegistry
17///
18/// StoreRegistry owns the generated mapping from schema `Store` paths to their
19/// row, index, and schema store handles.
20/// It validates registration invariants once at generated wiring time and then
21/// serves cheap immutable lookups during runtime operations.
22///
23
24#[derive(Default)]
25pub struct StoreRegistry {
26    stores: Vec<(&'static str, StoreHandle)>,
27}
28
29impl StoreRegistry {
30    /// Create an empty store registry.
31    #[must_use]
32    pub fn new() -> Self {
33        Self::default()
34    }
35
36    /// Iterate registered stores.
37    ///
38    /// Iteration order follows registration order. Semantic result ordering
39    /// must still not depend on this iteration order; callers that need
40    /// deterministic ordering must sort by store path.
41    pub fn iter(&self) -> impl Iterator<Item = (&'static str, StoreHandle)> {
42        self.stores.iter().copied()
43    }
44
45    /// Register a `Store` path to its row/index/schema store triplet with an
46    /// explicit allocation identity decision.
47    ///
48    /// Generated stable-store wiring supplies stable allocation identities.
49    /// Tests and future non-stable stores must pass
50    /// [`StoreAllocationIdentities::absent`] explicitly when allocation
51    /// identities are intentionally unavailable.
52    pub fn register_store(
53        &mut self,
54        name: &'static str,
55        data: &'static LocalKey<RefCell<DataStore>>,
56        index: &'static LocalKey<RefCell<IndexStore>>,
57        schema: &'static LocalKey<RefCell<SchemaStore>>,
58        allocations: StoreAllocationIdentities,
59        capabilities: StoreRuntimeStorageCapabilities,
60    ) -> Result<(), InternalError> {
61        if self
62            .stores
63            .iter()
64            .any(|(existing_name, _)| *existing_name == name)
65        {
66            return Err(StoreRegistryError::StoreAlreadyRegistered(name.to_string()).into());
67        }
68
69        // Keep one canonical logical store name per physical row/index/schema
70        // store triplet.
71        if let Some(existing_name) =
72            self.stores
73                .iter()
74                .find_map(|(existing_name, existing_handle)| {
75                    (std::ptr::eq(existing_handle.data_store(), data)
76                        && std::ptr::eq(existing_handle.index_store(), index)
77                        && std::ptr::eq(existing_handle.schema_store(), schema))
78                    .then_some(*existing_name)
79                })
80        {
81            return Err(StoreRegistryError::StoreHandleTripletAlreadyRegistered {
82                name: name.to_string(),
83                existing_name: existing_name.to_string(),
84            }
85            .into());
86        }
87
88        if allocations.allocation_identity_capability() != Some(capabilities.allocation_identity())
89        {
90            return Err(
91                StoreRegistryError::StoreAllocationCapabilityMismatch(name.to_string()).into(),
92            );
93        }
94
95        self.stores.push((
96            name,
97            StoreHandle::new(data, index, schema, 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(path.to_string()).into())
109    }
110}