Skip to main content

icydb_core/db/registry/
handle.rs

1use crate::db::{
2    data::DataStore,
3    index::{IndexState, IndexStore},
4    schema::SchemaStore,
5};
6use candid::CandidType;
7use serde::Deserialize;
8use std::{cell::RefCell, thread::LocalKey};
9
10///
11/// StoreHandle
12///
13/// StoreHandle binds the row, index, and schema stores for one generated schema
14/// `Store` path.
15/// It is the stable access token passed across commit, recovery, executor, and
16/// diagnostics boundaries instead of exposing registry internals directly.
17///
18
19#[derive(Clone, Copy, Debug)]
20pub struct StoreHandle {
21    data: &'static LocalKey<RefCell<DataStore>>,
22    index: &'static LocalKey<RefCell<IndexStore>>,
23    schema: &'static LocalKey<RefCell<SchemaStore>>,
24    allocations: StoreAllocationIdentities,
25    capabilities: StoreRuntimeStorageCapabilities,
26}
27
28/// Diagnostic storage mode carried by a runtime storage capability descriptor.
29///
30/// Policy code should branch on capability axes instead of this display value.
31#[derive(CandidType, Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
32pub enum StoreRuntimeStorageMode {
33    /// Durable stable-memory storage.
34    #[default]
35    Stable,
36    /// Volatile in-process heap storage.
37    Heap,
38}
39
40impl StoreRuntimeStorageMode {
41    /// Return the user-facing storage mode label.
42    #[must_use]
43    pub const fn as_str(self) -> &'static str {
44        match self {
45            Self::Stable => "stable",
46            Self::Heap => "heap",
47        }
48    }
49}
50
51/// Whether a store owns durable allocation identity.
52#[derive(CandidType, Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
53pub enum StoreAllocationIdentityCapability {
54    /// Stable allocation identity is present.
55    #[default]
56    Present,
57    /// Stable allocation identity is absent.
58    Absent,
59}
60
61impl StoreAllocationIdentityCapability {
62    /// Return the user-facing capability label.
63    #[must_use]
64    pub const fn as_str(self) -> &'static str {
65        match self {
66            Self::Present => "present",
67            Self::Absent => "absent",
68        }
69    }
70}
71
72/// Store durability class.
73#[derive(CandidType, Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
74pub enum StoreDurability {
75    /// Store contents participate in durable storage semantics.
76    #[default]
77    Durable,
78    /// Store contents are live-only and volatile.
79    Volatile,
80}
81
82impl StoreDurability {
83    /// Return the user-facing durability label.
84    #[must_use]
85    pub const fn as_str(self) -> &'static str {
86        match self {
87            Self::Durable => "durable",
88            Self::Volatile => "volatile",
89        }
90    }
91}
92
93/// Store recovery capability.
94#[derive(CandidType, Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
95pub enum StoreRecoveryCapability {
96    /// Store contents can be recovered through stable commit replay.
97    #[default]
98    StableCommitReplay,
99    /// Store contents are not recovered.
100    None,
101}
102
103impl StoreRecoveryCapability {
104    /// Return the user-facing recovery label.
105    #[must_use]
106    pub const fn as_str(self) -> &'static str {
107        match self {
108            Self::StableCommitReplay => "stable-replay",
109            Self::None => "none",
110        }
111    }
112}
113
114/// Store commit participation class.
115#[derive(CandidType, Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
116pub enum StoreCommitParticipation {
117    /// Store mutations participate in the durable commit path.
118    #[default]
119    Durable,
120    /// Store mutations are live-only side effects.
121    LiveOnly,
122}
123
124impl StoreCommitParticipation {
125    /// Return the user-facing commit-participation label.
126    #[must_use]
127    pub const fn as_str(self) -> &'static str {
128        match self {
129            Self::Durable => "durable",
130            Self::LiveOnly => "live-only",
131        }
132    }
133}
134
135/// Store schema metadata persistence class.
136#[derive(CandidType, Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
137pub enum StoreSchemaMetadataCapability {
138    /// Schema metadata has durable accepted-history semantics.
139    #[default]
140    DurableAcceptedHistory,
141    /// Schema metadata is rebuilt live and is not durable history.
142    LiveRebuiltMetadata,
143}
144
145impl StoreSchemaMetadataCapability {
146    /// Return the user-facing schema-metadata capability label.
147    #[must_use]
148    pub const fn as_str(self) -> &'static str {
149        match self {
150            Self::DurableAcceptedHistory => "durable-accepted-history",
151            Self::LiveRebuiltMetadata => "live-rebuilt-metadata",
152        }
153    }
154}
155
156/// Strong relation source capability for a store.
157#[derive(CandidType, Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
158pub enum StoreRelationSourceCapability {
159    /// Source rows can own durable relation integrity.
160    #[default]
161    DurableSource,
162    /// Source rows can participate in live relation validation.
163    LiveSource,
164}
165
166/// Strong relation target capability for a store.
167#[derive(CandidType, Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
168pub enum StoreRelationTargetCapability {
169    /// Target rows can be referenced by durable source rows.
170    #[default]
171    DurableTarget,
172    /// Target rows are volatile and cannot satisfy durable source integrity.
173    VolatileTarget,
174}
175
176/// Whether the store can participate in live validation.
177#[derive(CandidType, Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
178pub enum StoreLiveValidationCapability {
179    /// Live validation is supported.
180    #[default]
181    Supported,
182}
183
184/// Runtime storage capability descriptor carried by one registered store.
185///
186/// Capabilities describe storage policy. They are not allocation identity.
187#[derive(CandidType, Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
188pub struct StoreRuntimeStorageCapabilities {
189    storage_mode: StoreRuntimeStorageMode,
190    allocation_identity: StoreAllocationIdentityCapability,
191    durability: StoreDurability,
192    recovery: StoreRecoveryCapability,
193    commit_participation: StoreCommitParticipation,
194    schema_metadata: StoreSchemaMetadataCapability,
195    relation_source: StoreRelationSourceCapability,
196    relation_target: StoreRelationTargetCapability,
197    live_validation: StoreLiveValidationCapability,
198}
199
200impl StoreRuntimeStorageCapabilities {
201    /// Capability descriptor for stable-memory stores.
202    #[must_use]
203    pub const fn stable() -> Self {
204        Self {
205            storage_mode: StoreRuntimeStorageMode::Stable,
206            allocation_identity: StoreAllocationIdentityCapability::Present,
207            durability: StoreDurability::Durable,
208            recovery: StoreRecoveryCapability::StableCommitReplay,
209            commit_participation: StoreCommitParticipation::Durable,
210            schema_metadata: StoreSchemaMetadataCapability::DurableAcceptedHistory,
211            relation_source: StoreRelationSourceCapability::DurableSource,
212            relation_target: StoreRelationTargetCapability::DurableTarget,
213            live_validation: StoreLiveValidationCapability::Supported,
214        }
215    }
216
217    /// Capability descriptor for heap stores.
218    #[must_use]
219    pub const fn heap() -> Self {
220        Self {
221            storage_mode: StoreRuntimeStorageMode::Heap,
222            allocation_identity: StoreAllocationIdentityCapability::Absent,
223            durability: StoreDurability::Volatile,
224            recovery: StoreRecoveryCapability::None,
225            commit_participation: StoreCommitParticipation::LiveOnly,
226            schema_metadata: StoreSchemaMetadataCapability::LiveRebuiltMetadata,
227            relation_source: StoreRelationSourceCapability::LiveSource,
228            relation_target: StoreRelationTargetCapability::VolatileTarget,
229            live_validation: StoreLiveValidationCapability::Supported,
230        }
231    }
232
233    /// Diagnostic storage mode. Policy code should use the capability axes.
234    #[must_use]
235    pub const fn storage_mode(self) -> StoreRuntimeStorageMode {
236        self.storage_mode
237    }
238
239    /// Allocation identity capability.
240    #[must_use]
241    pub const fn allocation_identity(self) -> StoreAllocationIdentityCapability {
242        self.allocation_identity
243    }
244
245    /// Durability capability.
246    #[must_use]
247    pub const fn durability(self) -> StoreDurability {
248        self.durability
249    }
250
251    /// Recovery capability.
252    #[must_use]
253    pub const fn recovery(self) -> StoreRecoveryCapability {
254        self.recovery
255    }
256
257    /// Commit participation capability.
258    #[must_use]
259    pub const fn commit_participation(self) -> StoreCommitParticipation {
260        self.commit_participation
261    }
262
263    /// Schema metadata persistence capability.
264    #[must_use]
265    pub const fn schema_metadata(self) -> StoreSchemaMetadataCapability {
266        self.schema_metadata
267    }
268
269    /// Relation source capability.
270    #[must_use]
271    pub const fn relation_source(self) -> StoreRelationSourceCapability {
272        self.relation_source
273    }
274
275    /// Relation target capability.
276    #[must_use]
277    pub const fn relation_target(self) -> StoreRelationTargetCapability {
278        self.relation_target
279    }
280
281    /// Live validation capability.
282    #[must_use]
283    pub const fn live_validation(self) -> StoreLiveValidationCapability {
284        self.live_validation
285    }
286}
287
288///
289/// StoreAllocationIdentity
290///
291/// Durable allocation identity for one physical stable-memory role.
292///
293
294#[derive(Clone, Copy, Debug, Eq, PartialEq)]
295pub struct StoreAllocationIdentity {
296    memory_id: u8,
297    stable_key: &'static str,
298}
299
300impl StoreAllocationIdentity {
301    /// Build one stable allocation identity descriptor.
302    #[must_use]
303    pub const fn new(memory_id: u8, stable_key: &'static str) -> Self {
304        Self {
305            memory_id,
306            stable_key,
307        }
308    }
309
310    /// Stable-memory manager ID.
311    #[must_use]
312    pub const fn memory_id(self) -> u8 {
313        self.memory_id
314    }
315
316    /// Durable stable-memory key.
317    #[must_use]
318    pub const fn stable_key(self) -> &'static str {
319        self.stable_key
320    }
321}
322
323///
324/// StoreAllocationIdentities
325///
326/// Durable allocation identities for one logical store's data, index, and
327/// schema memories.
328///
329
330#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
331pub struct StoreAllocationIdentities {
332    data: Option<StoreAllocationIdentity>,
333    index: Option<StoreAllocationIdentity>,
334    schema: Option<StoreAllocationIdentity>,
335}
336
337impl StoreAllocationIdentities {
338    /// Build an absent allocation identity bundle.
339    #[must_use]
340    pub const fn absent() -> Self {
341        Self {
342            data: None,
343            index: None,
344            schema: None,
345        }
346    }
347
348    /// Build one allocation identity bundle.
349    #[must_use]
350    pub const fn new(
351        data: StoreAllocationIdentity,
352        index: StoreAllocationIdentity,
353        schema: StoreAllocationIdentity,
354    ) -> Self {
355        Self {
356            data: Some(data),
357            index: Some(index),
358            schema: Some(schema),
359        }
360    }
361
362    /// Return data-memory allocation identity.
363    #[must_use]
364    pub const fn data(self) -> Option<StoreAllocationIdentity> {
365        self.data
366    }
367
368    /// Return index-memory allocation identity.
369    #[must_use]
370    pub const fn index(self) -> Option<StoreAllocationIdentity> {
371        self.index
372    }
373
374    /// Return schema-memory allocation identity.
375    #[must_use]
376    pub const fn schema(self) -> Option<StoreAllocationIdentity> {
377        self.schema
378    }
379
380    /// Return the allocation capability represented by this triplet, or
381    /// `None` if the triplet is partially populated and therefore invalid.
382    #[must_use]
383    pub const fn allocation_identity_capability(self) -> Option<StoreAllocationIdentityCapability> {
384        match (self.data, self.index, self.schema) {
385            (Some(_), Some(_), Some(_)) => Some(StoreAllocationIdentityCapability::Present),
386            (None, None, None) => Some(StoreAllocationIdentityCapability::Absent),
387            _ => None,
388        }
389    }
390}
391
392impl StoreHandle {
393    /// Build a store handle with an explicit allocation identity decision.
394    #[must_use]
395    pub const fn new(
396        data: &'static LocalKey<RefCell<DataStore>>,
397        index: &'static LocalKey<RefCell<IndexStore>>,
398        schema: &'static LocalKey<RefCell<SchemaStore>>,
399        allocations: StoreAllocationIdentities,
400        capabilities: StoreRuntimeStorageCapabilities,
401    ) -> Self {
402        Self {
403            data,
404            index,
405            schema,
406            allocations,
407            capabilities,
408        }
409    }
410
411    /// Borrow the row store immutably.
412    pub fn with_data<R>(&self, f: impl FnOnce(&DataStore) -> R) -> R {
413        #[cfg(feature = "diagnostics")]
414        {
415            crate::db::physical_access::measure_physical_access_operation(|| {
416                self.data.with_borrow(f)
417            })
418        }
419
420        #[cfg(not(feature = "diagnostics"))]
421        {
422            self.data.with_borrow(f)
423        }
424    }
425
426    /// Borrow the row store mutably.
427    pub fn with_data_mut<R>(&self, f: impl FnOnce(&mut DataStore) -> R) -> R {
428        self.data.with_borrow_mut(f)
429    }
430
431    /// Borrow the index store immutably.
432    pub fn with_index<R>(&self, f: impl FnOnce(&IndexStore) -> R) -> R {
433        #[cfg(feature = "diagnostics")]
434        {
435            crate::db::physical_access::measure_physical_access_operation(|| {
436                self.index.with_borrow(f)
437            })
438        }
439
440        #[cfg(not(feature = "diagnostics"))]
441        {
442            self.index.with_borrow(f)
443        }
444    }
445
446    /// Borrow the index store mutably.
447    pub fn with_index_mut<R>(&self, f: impl FnOnce(&mut IndexStore) -> R) -> R {
448        self.index.with_borrow_mut(f)
449    }
450
451    /// Borrow the schema store immutably.
452    pub fn with_schema<R>(&self, f: impl FnOnce(&SchemaStore) -> R) -> R {
453        self.schema.with_borrow(f)
454    }
455
456    /// Borrow the schema store mutably.
457    pub fn with_schema_mut<R>(&self, f: impl FnOnce(&mut SchemaStore) -> R) -> R {
458        self.schema.with_borrow_mut(f)
459    }
460
461    /// Return the explicit lifecycle state of the bound index store.
462    #[must_use]
463    pub(in crate::db) fn index_state(&self) -> IndexState {
464        self.with_index(IndexStore::state)
465    }
466
467    /// Return whether this handle's data store is heap-backed and volatile.
468    #[must_use]
469    pub(in crate::db) const fn data_is_heap_storage(&self) -> bool {
470        matches!(
471            self.capabilities.storage_mode(),
472            StoreRuntimeStorageMode::Heap
473        )
474    }
475
476    /// Mark the bound index store as Building.
477    pub(in crate::db) fn mark_index_building(&self) {
478        self.with_index_mut(IndexStore::mark_building);
479    }
480
481    /// Mark the bound index store as Ready.
482    pub(in crate::db) fn mark_index_ready(&self) {
483        self.with_index_mut(IndexStore::mark_ready);
484    }
485
486    /// Return the raw row-store accessor.
487    #[must_use]
488    pub const fn data_store(&self) -> &'static LocalKey<RefCell<DataStore>> {
489        self.data
490    }
491
492    /// Return the raw index-store accessor.
493    #[must_use]
494    pub const fn index_store(&self) -> &'static LocalKey<RefCell<IndexStore>> {
495        self.index
496    }
497
498    /// Return the raw schema-store accessor.
499    #[must_use]
500    pub const fn schema_store(&self) -> &'static LocalKey<RefCell<SchemaStore>> {
501        self.schema
502    }
503
504    /// Return the data-memory allocation identity when generated wiring
505    /// supplied it.
506    #[must_use]
507    pub const fn data_allocation(&self) -> Option<StoreAllocationIdentity> {
508        self.allocations.data()
509    }
510
511    /// Return the index-memory allocation identity when generated wiring
512    /// supplied it.
513    #[must_use]
514    pub const fn index_allocation(&self) -> Option<StoreAllocationIdentity> {
515        self.allocations.index()
516    }
517
518    /// Return the schema-memory allocation identity when generated wiring
519    /// supplied it.
520    #[must_use]
521    pub const fn schema_allocation(&self) -> Option<StoreAllocationIdentity> {
522        self.allocations.schema()
523    }
524
525    /// Return this store's explicit runtime storage capabilities.
526    #[must_use]
527    pub const fn storage_capabilities(&self) -> StoreRuntimeStorageCapabilities {
528        self.capabilities
529    }
530}