Skip to main content

icydb_core/db/registry/
handle.rs

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