Skip to main content

icydb_core/db/registry/
handle.rs

1//! Module: db::registry::handle
2//! Responsibility: stable store handles and runtime storage capability descriptors.
3//! Does not own: registry path lookup or store mutation semantics.
4//! Boundary: exposes registered storage roles without exposing registry internals.
5
6use crate::db::{
7    commit::database_incarnation_id,
8    data::DataStore,
9    index::{IndexId, IndexKeyKind, IndexState, IndexStore, UserIndexPrefixCardinalityKey},
10    integrity::DatabaseIncarnationId,
11    journal::{FoldWatermark, JournalTailStore},
12    schema::{
13        SchemaStore,
14        cardinality_build::CardinalityBuildAuthority,
15        cardinality_generation::{
16            CardinalityAcceptedRootIdentity, CardinalityCountDigest, CardinalityGenerationState,
17            CardinalityStoreAllocationIdentity,
18        },
19    },
20};
21use crate::{error::InternalError, types::EntityTag};
22use candid::CandidType;
23use serde::Deserialize;
24use std::{cell::RefCell, thread::LocalKey};
25
26///
27/// StoreHandle
28///
29/// StoreHandle binds the row, index, and schema stores for one generated schema
30/// `Store` path.
31/// It is the stable access token passed across commit, recovery, executor, and
32/// diagnostics boundaries instead of exposing registry internals directly.
33///
34
35#[derive(Clone, Copy, Debug)]
36pub struct StoreHandle {
37    data: &'static LocalKey<RefCell<DataStore>>,
38    index: &'static LocalKey<RefCell<IndexStore>>,
39    schema: &'static LocalKey<RefCell<SchemaStore>>,
40    journal: Option<&'static LocalKey<RefCell<JournalTailStore>>>,
41    allocations: StoreAllocationIdentities,
42    cardinality_allocation: Option<CardinalityStoreAllocationIdentity>,
43    capabilities: StoreRuntimeStorageCapabilities,
44}
45
46enum ReadyCardinalityCountTargets<'a> {
47    Digests(&'a [CardinalityCountDigest]),
48    UserIndexPrefixes(&'a [UserIndexPrefixCardinalityKey]),
49}
50
51enum ReadyCardinalitySource {
52    Current {
53        database_incarnation: DatabaseIncarnationId,
54    },
55    Admitted {
56        database_incarnation: DatabaseIncarnationId,
57        accepted_root: CardinalityAcceptedRootIdentity,
58        fold_watermark: FoldWatermark,
59    },
60}
61
62impl ReadyCardinalityCountTargets<'_> {
63    const fn len(&self) -> usize {
64        match self {
65            Self::Digests(digests) => digests.len(),
66            Self::UserIndexPrefixes(keys) => keys.len(),
67        }
68    }
69}
70
71/// Diagnostic storage mode carried by a runtime storage capability descriptor.
72///
73/// Policy code should branch on capability axes instead of this display value.
74#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
75pub enum StoreRuntimeStorageMode {
76    /// Volatile in-process heap storage.
77    #[default]
78    Heap,
79    /// Journaled cached-stable durable storage.
80    Journaled,
81}
82
83impl StoreRuntimeStorageMode {
84    /// Return the user-facing storage mode label.
85    #[must_use]
86    pub const fn as_str(self) -> &'static str {
87        match self {
88            Self::Heap => "heap",
89            Self::Journaled => "journaled",
90        }
91    }
92}
93
94/// Whether a store owns durable allocation identity.
95#[derive(CandidType, Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
96pub enum StoreAllocationIdentityCapability {
97    /// Stable allocation identity is present.
98    #[default]
99    Present,
100    /// Stable allocation identity is absent.
101    Absent,
102}
103
104impl StoreAllocationIdentityCapability {
105    /// Return the user-facing capability label.
106    #[must_use]
107    pub const fn as_str(self) -> &'static str {
108        match self {
109            Self::Present => "present",
110            Self::Absent => "absent",
111        }
112    }
113}
114
115/// Store durability class.
116#[derive(CandidType, Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
117pub enum StoreDurability {
118    /// Store contents participate in durable storage semantics.
119    #[default]
120    Durable,
121    /// Store contents are live-only and volatile.
122    Volatile,
123}
124
125impl StoreDurability {
126    /// Return the user-facing durability label.
127    #[must_use]
128    pub const fn as_str(self) -> &'static str {
129        match self {
130            Self::Durable => "durable",
131            Self::Volatile => "volatile",
132        }
133    }
134}
135
136/// Store recovery capability.
137#[derive(CandidType, Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
138pub enum StoreRecoveryCapability {
139    /// Store contents can be recovered from canonical stable BTrees plus a
140    /// committed journal tail.
141    #[default]
142    StableBasePlusJournalReplay,
143    /// Store contents are not recovered.
144    None,
145}
146
147impl StoreRecoveryCapability {
148    /// Return the user-facing recovery label.
149    #[must_use]
150    pub const fn as_str(self) -> &'static str {
151        match self {
152            Self::StableBasePlusJournalReplay => "stable-base-plus-journal-replay",
153            Self::None => "none",
154        }
155    }
156}
157
158/// Store commit participation class.
159#[derive(CandidType, Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
160pub enum StoreCommitParticipation {
161    /// Store mutations participate in the durable commit path.
162    #[default]
163    Durable,
164    /// Store mutations are live-only side effects.
165    LiveOnly,
166}
167
168impl StoreCommitParticipation {
169    /// Return the user-facing commit-participation label.
170    #[must_use]
171    pub const fn as_str(self) -> &'static str {
172        match self {
173            Self::Durable => "durable",
174            Self::LiveOnly => "live-only",
175        }
176    }
177}
178
179/// Store schema metadata persistence class.
180#[derive(CandidType, Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
181pub enum StoreSchemaMetadataCapability {
182    /// The store-local projection is rebuilt from a durable accepted checkpoint
183    /// and does not retain its own schema history.
184    LiveRebuiltMetadata,
185    /// Schema metadata is canonical stable history plus committed journal tail.
186    #[default]
187    CanonicalStableHistoryPlusJournalTail,
188}
189
190impl StoreSchemaMetadataCapability {
191    /// Return the user-facing schema-metadata capability label.
192    #[must_use]
193    pub const fn as_str(self) -> &'static str {
194        match self {
195            Self::LiveRebuiltMetadata => "live-rebuilt-metadata",
196            Self::CanonicalStableHistoryPlusJournalTail => {
197                "canonical-stable-history-plus-journal-tail"
198            }
199        }
200    }
201}
202
203/// Relation source capability for a store.
204#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
205pub enum StoreRelationSourceCapability {
206    /// Source rows can own durable relation integrity.
207    #[default]
208    DurableSource,
209    /// Source rows can participate in live relation validation.
210    LiveSource,
211}
212
213/// Relation target capability for a store.
214#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
215pub enum StoreRelationTargetCapability {
216    /// Target rows can be referenced by durable source rows.
217    #[default]
218    DurableTarget,
219    /// Target rows are volatile and cannot satisfy durable source integrity.
220    VolatileTarget,
221}
222
223/// Runtime storage capability descriptor carried by one registered store.
224///
225/// Capabilities describe storage policy. They are not allocation identity.
226#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
227pub struct StoreRuntimeStorageCapabilities {
228    storage_mode: StoreRuntimeStorageMode,
229    allocation_identity: StoreAllocationIdentityCapability,
230    durability: StoreDurability,
231    recovery: StoreRecoveryCapability,
232    commit_participation: StoreCommitParticipation,
233    schema_metadata: StoreSchemaMetadataCapability,
234    relation_source: StoreRelationSourceCapability,
235    relation_target: StoreRelationTargetCapability,
236}
237
238impl StoreRuntimeStorageCapabilities {
239    /// Capability descriptor for heap stores.
240    #[must_use]
241    pub const fn heap() -> Self {
242        Self {
243            storage_mode: StoreRuntimeStorageMode::Heap,
244            allocation_identity: StoreAllocationIdentityCapability::Absent,
245            durability: StoreDurability::Volatile,
246            recovery: StoreRecoveryCapability::None,
247            commit_participation: StoreCommitParticipation::LiveOnly,
248            schema_metadata: StoreSchemaMetadataCapability::LiveRebuiltMetadata,
249            relation_source: StoreRelationSourceCapability::LiveSource,
250            relation_target: StoreRelationTargetCapability::VolatileTarget,
251        }
252    }
253
254    /// Capability descriptor for journaled cached-stable stores.
255    #[must_use]
256    pub const fn journaled() -> Self {
257        Self {
258            storage_mode: StoreRuntimeStorageMode::Journaled,
259            allocation_identity: StoreAllocationIdentityCapability::Present,
260            durability: StoreDurability::Durable,
261            recovery: StoreRecoveryCapability::StableBasePlusJournalReplay,
262            commit_participation: StoreCommitParticipation::Durable,
263            schema_metadata: StoreSchemaMetadataCapability::CanonicalStableHistoryPlusJournalTail,
264            relation_source: StoreRelationSourceCapability::DurableSource,
265            relation_target: StoreRelationTargetCapability::DurableTarget,
266        }
267    }
268
269    /// Diagnostic storage mode. Policy code should use the capability axes.
270    #[must_use]
271    pub const fn storage_mode(self) -> StoreRuntimeStorageMode {
272        self.storage_mode
273    }
274
275    /// Allocation identity capability.
276    #[must_use]
277    pub const fn allocation_identity(self) -> StoreAllocationIdentityCapability {
278        self.allocation_identity
279    }
280
281    /// Durability capability.
282    #[must_use]
283    pub const fn durability(self) -> StoreDurability {
284        self.durability
285    }
286
287    /// Recovery capability.
288    #[must_use]
289    pub const fn recovery(self) -> StoreRecoveryCapability {
290        self.recovery
291    }
292
293    /// Commit participation capability.
294    #[must_use]
295    pub const fn commit_participation(self) -> StoreCommitParticipation {
296        self.commit_participation
297    }
298
299    /// Schema metadata persistence capability.
300    #[must_use]
301    pub const fn schema_metadata(self) -> StoreSchemaMetadataCapability {
302        self.schema_metadata
303    }
304
305    /// Relation source capability.
306    #[must_use]
307    pub const fn relation_source(self) -> StoreRelationSourceCapability {
308        self.relation_source
309    }
310
311    /// Relation target capability.
312    #[must_use]
313    pub const fn relation_target(self) -> StoreRelationTargetCapability {
314        self.relation_target
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 journaled cached-stable allocation identity bundle.
381    #[must_use]
382    pub const fn new_journaled(
383        data: StoreAllocationIdentity,
384        index: StoreAllocationIdentity,
385        schema: StoreAllocationIdentity,
386        journal: StoreAllocationIdentity,
387    ) -> Self {
388        Self {
389            data: Some(data),
390            index: Some(index),
391            schema: Some(schema),
392            journal: Some(journal),
393        }
394    }
395
396    /// Return data-memory allocation identity.
397    #[must_use]
398    pub const fn data(self) -> Option<StoreAllocationIdentity> {
399        self.data
400    }
401
402    /// Return index-memory allocation identity.
403    #[must_use]
404    pub const fn index(self) -> Option<StoreAllocationIdentity> {
405        self.index
406    }
407
408    /// Return schema-memory allocation identity.
409    #[must_use]
410    pub const fn schema(self) -> Option<StoreAllocationIdentity> {
411        self.schema
412    }
413
414    /// Return journal-tail allocation identity.
415    #[must_use]
416    pub const fn journal(self) -> Option<StoreAllocationIdentity> {
417        self.journal
418    }
419
420    /// Return the allocation capability represented by this triplet, or
421    /// `None` if the triplet is partially populated and therefore invalid.
422    #[must_use]
423    pub const fn allocation_identity_capability(self) -> Option<StoreAllocationIdentityCapability> {
424        match (self.data, self.index, self.schema) {
425            (Some(_), Some(_), Some(_)) => Some(StoreAllocationIdentityCapability::Present),
426            (None, None, None) if self.journal.is_none() => {
427                Some(StoreAllocationIdentityCapability::Absent)
428            }
429            _ => None,
430        }
431    }
432
433    /// Return whether this allocation shape matches the concrete storage
434    /// capability descriptor.
435    #[must_use]
436    pub const fn matches_storage_capabilities(
437        self,
438        capabilities: StoreRuntimeStorageCapabilities,
439    ) -> bool {
440        match capabilities.storage_mode() {
441            StoreRuntimeStorageMode::Heap => {
442                self.data.is_none()
443                    && self.index.is_none()
444                    && self.schema.is_none()
445                    && self.journal.is_none()
446            }
447            StoreRuntimeStorageMode::Journaled => {
448                self.data.is_some()
449                    && self.index.is_some()
450                    && self.schema.is_some()
451                    && self.journal.is_some()
452            }
453        }
454    }
455}
456
457impl StoreHandle {
458    /// Build a store handle with an explicit allocation identity decision.
459    #[must_use]
460    pub const fn new(
461        data: &'static LocalKey<RefCell<DataStore>>,
462        index: &'static LocalKey<RefCell<IndexStore>>,
463        schema: &'static LocalKey<RefCell<SchemaStore>>,
464        allocations: StoreAllocationIdentities,
465        capabilities: StoreRuntimeStorageCapabilities,
466    ) -> Self {
467        Self {
468            data,
469            index,
470            schema,
471            journal: None,
472            allocations,
473            cardinality_allocation: None,
474            capabilities,
475        }
476    }
477
478    /// Build a journaled store handle with an explicit journal-tail store.
479    #[must_use]
480    pub fn new_journaled(
481        data: &'static LocalKey<RefCell<DataStore>>,
482        index: &'static LocalKey<RefCell<IndexStore>>,
483        schema: &'static LocalKey<RefCell<SchemaStore>>,
484        journal: &'static LocalKey<RefCell<JournalTailStore>>,
485        allocations: StoreAllocationIdentities,
486        capabilities: StoreRuntimeStorageCapabilities,
487    ) -> Self {
488        let cardinality_allocation = CardinalityStoreAllocationIdentity::derive(allocations).ok();
489        Self {
490            data,
491            index,
492            schema,
493            journal: Some(journal),
494            allocations,
495            cardinality_allocation,
496            capabilities,
497        }
498    }
499
500    /// Borrow the row store immutably.
501    pub fn with_data<R>(&self, f: impl FnOnce(&DataStore) -> R) -> R {
502        #[cfg(feature = "diagnostics")]
503        {
504            crate::db::physical_access::measure_physical_access_operation(|| {
505                self.data.with_borrow(f)
506            })
507        }
508
509        #[cfg(not(feature = "diagnostics"))]
510        {
511            self.data.with_borrow(f)
512        }
513    }
514
515    /// Borrow the row store mutably.
516    pub fn with_data_mut<R>(&self, f: impl FnOnce(&mut DataStore) -> R) -> R {
517        self.data.with_borrow_mut(f)
518    }
519
520    /// Borrow the index store immutably.
521    pub fn with_index<R>(&self, f: impl FnOnce(&IndexStore) -> R) -> R {
522        #[cfg(feature = "diagnostics")]
523        {
524            crate::db::physical_access::measure_physical_access_operation(|| {
525                self.index.with_borrow(f)
526            })
527        }
528
529        #[cfg(not(feature = "diagnostics"))]
530        {
531            self.index.with_borrow(f)
532        }
533    }
534
535    /// Borrow the index store mutably.
536    pub fn with_index_mut<R>(&self, f: impl FnOnce(&mut IndexStore) -> R) -> R {
537        self.index.with_borrow_mut(f)
538    }
539
540    /// Borrow the schema store immutably.
541    pub fn with_schema<R>(&self, f: impl FnOnce(&SchemaStore) -> R) -> R {
542        self.schema.with_borrow(f)
543    }
544
545    /// Borrow the schema store mutably.
546    pub fn with_schema_mut<R>(&self, f: impl FnOnce(&mut SchemaStore) -> R) -> R {
547        self.schema.with_borrow_mut(f)
548    }
549
550    /// Return exact visible entity cardinality through the store's canonical proof boundary.
551    #[must_use]
552    pub(in crate::db) fn exact_entity_count(&self, entity: EntityTag) -> Option<u64> {
553        if self.journal.is_none() {
554            return self.with_data(|store| store.exact_entity_count(entity));
555        }
556        let delta = self.with_data(|store| store.exact_entity_cardinality_delta(entity))?;
557        let digest = CardinalityCountDigest::for_entity(entity);
558        let base = self
559            .ready_cardinality_counts(&[digest], |authority| authority.accepts_entity(entity))
560            .ok()
561            .flatten()?
562            .into_iter()
563            .next()?;
564        apply_visible_cardinality_delta(base, delta)
565    }
566
567    /// Return exact visible cardinality for one accepted user-index prefix.
568    #[must_use]
569    pub(in crate::db) fn exact_user_index_prefix_count(
570        &self,
571        data_generation: u64,
572        key_kind: IndexKeyKind,
573        index_id: IndexId,
574        components: &[Vec<u8>],
575    ) -> Option<u64> {
576        self.exact_user_index_prefix_counts(data_generation, key_kind, index_id, [components])?
577            .into_iter()
578            .next()
579    }
580
581    /// Return exact visible counts for prefixes on one accepted user index.
582    #[must_use]
583    pub(in crate::db) fn exact_user_index_prefix_counts<'a>(
584        &self,
585        data_generation: u64,
586        key_kind: IndexKeyKind,
587        index_id: IndexId,
588        component_prefixes: impl IntoIterator<Item = &'a [Vec<u8>]>,
589    ) -> Option<Vec<u64>> {
590        let component_prefixes = component_prefixes.into_iter().collect::<Vec<_>>();
591        if key_kind != IndexKeyKind::User {
592            return None;
593        }
594        if self.journal.is_none() {
595            return self.with_index(|store| {
596                component_prefixes
597                    .iter()
598                    .map(|components| {
599                        store.exact_prefix_cardinality(
600                            data_generation,
601                            key_kind,
602                            index_id,
603                            components,
604                        )
605                    })
606                    .collect()
607            });
608        }
609        let deltas = self.with_index(|store| {
610            component_prefixes
611                .iter()
612                .map(|components| {
613                    store.exact_prefix_cardinality_delta(key_kind, index_id, components)
614                })
615                .collect::<Option<Vec<_>>>()
616        })?;
617        let digests = component_prefixes
618            .iter()
619            .map(|components| {
620                CardinalityCountDigest::for_user_index_prefix(index_id, components).ok()
621            })
622            .collect::<Option<Vec<_>>>()?;
623        let bases = self
624            .ready_cardinality_counts(&digests, |authority| {
625                component_prefixes.iter().all(|components| {
626                    authority.accepts_user_index_prefix(index_id, components.len())
627                })
628            })
629            .ok()
630            .flatten()?;
631        bases
632            .into_iter()
633            .zip(deltas)
634            .map(|(base, delta)| apply_visible_cardinality_delta(base, delta))
635            .collect()
636    }
637
638    /// Return exact visible counts for accepted prefix keys across user indexes.
639    #[must_use]
640    pub(in crate::db) fn exact_user_index_prefix_key_counts(
641        &self,
642        data_generation: u64,
643        keys: &[UserIndexPrefixCardinalityKey],
644    ) -> Option<Vec<u64>> {
645        self.exact_user_index_prefix_key_counts_with_authority(data_generation, keys, None)
646    }
647
648    /// Return exact prefix counts through an accepted root admitted in this request.
649    #[must_use]
650    pub(in crate::db) fn exact_user_index_prefix_key_counts_for_admitted_root(
651        &self,
652        database_incarnation: DatabaseIncarnationId,
653        accepted_root: CardinalityAcceptedRootIdentity,
654        data_generation: u64,
655        keys: &[UserIndexPrefixCardinalityKey],
656    ) -> Option<Vec<u64>> {
657        self.exact_user_index_prefix_key_counts_with_authority(
658            data_generation,
659            keys,
660            Some((database_incarnation, accepted_root)),
661        )
662    }
663
664    fn exact_user_index_prefix_key_counts_with_authority(
665        &self,
666        data_generation: u64,
667        keys: &[UserIndexPrefixCardinalityKey],
668        admitted: Option<(DatabaseIncarnationId, CardinalityAcceptedRootIdentity)>,
669    ) -> Option<Vec<u64>> {
670        if keys.is_empty() {
671            return None;
672        }
673        if self.journal.is_none() {
674            return self.with_index(|store| {
675                keys.iter()
676                    .map(|key| {
677                        store.exact_prefix_cardinality(
678                            data_generation,
679                            IndexKeyKind::User,
680                            key.index_id(),
681                            key.prefix_components(),
682                        )
683                    })
684                    .collect()
685            });
686        }
687        let (delta_watermark, deltas) = self.with_index(|store| {
688            let watermark = store.exact_prefix_cardinality_delta_watermark()?;
689            keys.iter()
690                .map(|key| {
691                    store.exact_prefix_cardinality_delta(
692                        IndexKeyKind::User,
693                        key.index_id(),
694                        key.prefix_components(),
695                    )
696                })
697                .collect::<Option<Vec<_>>>()
698                .map(|deltas| (watermark, deltas))
699        })?;
700        let accepts = |authority: &CardinalityBuildAuthority| {
701            keys.iter().all(|key| {
702                authority.accepts_user_index_prefix(key.index_id(), key.prefix_components().len())
703            })
704        };
705        let bases = match admitted {
706            Some((database_incarnation, accepted_root)) => self
707                .ready_cardinality_counts_for_source(
708                    ReadyCardinalitySource::Admitted {
709                        database_incarnation,
710                        accepted_root,
711                        fold_watermark: delta_watermark,
712                    },
713                    ReadyCardinalityCountTargets::UserIndexPrefixes(keys),
714                    accepts,
715                ),
716            None => self.ready_cardinality_counts_for_targets(
717                ReadyCardinalityCountTargets::UserIndexPrefixes(keys),
718                accepts,
719            ),
720        }
721        .ok()
722        .flatten()?;
723        bases
724            .into_iter()
725            .zip(deltas)
726            .map(|(base, delta)| apply_visible_cardinality_delta(base, delta))
727            .collect()
728    }
729
730    /// Prove that one accepted user-index prefix family has a synchronized Ready generation.
731    ///
732    /// This does not expose or infer count values. Callers may retain every
733    /// branch conservatively, but must use the exact count methods above before
734    /// pruning any branch as empty.
735    #[must_use]
736    pub(in crate::db) fn user_index_prefix_family_has_ready_generation<'a, I>(
737        &self,
738        database_incarnation: DatabaseIncarnationId,
739        accepted_root: CardinalityAcceptedRootIdentity,
740        data_generation: u64,
741        key_kind: IndexKeyKind,
742        index_id: IndexId,
743        component_prefixes: I,
744    ) -> bool
745    where
746        I: Clone + IntoIterator<Item = &'a [Vec<u8>]>,
747    {
748        if key_kind != IndexKeyKind::User || component_prefixes.clone().into_iter().next().is_none()
749        {
750            return false;
751        }
752        if self.journal.is_none() {
753            return self.with_index(|store| {
754                component_prefixes.clone().into_iter().all(|components| {
755                    store
756                        .exact_prefix_cardinality(data_generation, key_kind, index_id, components)
757                        .is_some()
758                })
759            });
760        }
761        let delta_watermark = self.with_index(|store| {
762            let watermark = store.exact_prefix_cardinality_delta_watermark()?;
763            component_prefixes
764                .clone()
765                .into_iter()
766                .all(|components| {
767                    store
768                        .exact_prefix_cardinality_delta(key_kind, index_id, components)
769                        .is_some()
770                })
771                .then_some(watermark)
772        });
773        delta_watermark.is_some_and(|watermark| {
774            self.ready_cardinality_counts_for_source(
775                ReadyCardinalitySource::Admitted {
776                    database_incarnation,
777                    accepted_root,
778                    fold_watermark: watermark,
779                },
780                ReadyCardinalityCountTargets::Digests(&[]),
781                |authority| {
782                    component_prefixes.into_iter().all(|components| {
783                        authority.accepts_user_index_prefix(index_id, components.len())
784                    })
785                },
786            )
787            .is_ok_and(|counts| counts.is_some())
788        })
789    }
790
791    /// Sum exact visible counts for prefixes on one accepted user index.
792    #[must_use]
793    pub(in crate::db) fn exact_user_index_prefix_count_sum<'a>(
794        &self,
795        data_generation: u64,
796        key_kind: IndexKeyKind,
797        index_id: IndexId,
798        component_prefixes: impl IntoIterator<Item = &'a [Vec<u8>]>,
799        stop_after: Option<u64>,
800    ) -> Option<u64> {
801        let component_prefixes = component_prefixes.into_iter().collect::<Vec<_>>();
802        if self.journal.is_none() {
803            return self.with_index(|store| {
804                store.exact_prefix_cardinality_sum(
805                    data_generation,
806                    key_kind,
807                    index_id,
808                    component_prefixes.iter().copied(),
809                    stop_after,
810                )
811            });
812        }
813        let counts = self.exact_user_index_prefix_counts(
814            data_generation,
815            key_kind,
816            index_id,
817            component_prefixes.iter().copied(),
818        )?;
819        let mut total = 0_u64;
820        for count in counts {
821            total = total.checked_add(count)?;
822            if stop_after.is_some_and(|required| total >= required) {
823                break;
824            }
825        }
826        Some(total)
827    }
828
829    /// Enumerate one bounded child-prefix family proven complete by exact parent/child totals.
830    #[must_use]
831    pub(in crate::db) fn exact_user_index_child_prefixes_for_parent_set<'a>(
832        &self,
833        data_generation: u64,
834        index_id: IndexId,
835        parent_prefixes: impl IntoIterator<Item = &'a [Vec<u8>]>,
836        total_cap: usize,
837    ) -> Option<Vec<Vec<Vec<u8>>>> {
838        let mut parent_prefixes = parent_prefixes
839            .into_iter()
840            .map(<[Vec<u8>]>::to_vec)
841            .collect::<Vec<_>>();
842        if parent_prefixes.iter().any(Vec::is_empty) {
843            return None;
844        }
845        parent_prefixes.sort_unstable();
846        parent_prefixes.dedup();
847        let child_prefixes = self.with_index(|store| {
848            store.exact_child_prefixes_for_parent_set(
849                data_generation,
850                IndexKeyKind::User,
851                index_id,
852                parent_prefixes.iter().map(Vec::as_slice),
853                total_cap,
854            )
855        })?;
856        if self.journal.is_none() {
857            return Some(child_prefixes);
858        }
859        let parent_count = parent_prefixes.len();
860        let counts = self.exact_user_index_prefix_counts(
861            data_generation,
862            IndexKeyKind::User,
863            index_id,
864            parent_prefixes
865                .iter()
866                .chain(&child_prefixes)
867                .map(Vec::as_slice),
868        )?;
869        let (parent_counts, child_counts) = counts.split_at(parent_count);
870        let parent_total = checked_cardinality_sum(parent_counts)?;
871        let child_total = checked_cardinality_sum(child_counts)?;
872        (parent_total == child_total).then_some(child_prefixes)
873    }
874
875    fn ready_cardinality_counts(
876        &self,
877        digests: &[CardinalityCountDigest],
878        accepts: impl FnOnce(&CardinalityBuildAuthority) -> bool,
879    ) -> Result<Option<Vec<u64>>, InternalError> {
880        self.ready_cardinality_counts_for_targets(
881            ReadyCardinalityCountTargets::Digests(digests),
882            accepts,
883        )
884    }
885
886    fn ready_cardinality_counts_for_targets(
887        &self,
888        targets: ReadyCardinalityCountTargets<'_>,
889        accepts: impl FnOnce(&CardinalityBuildAuthority) -> bool,
890    ) -> Result<Option<Vec<u64>>, InternalError> {
891        let incarnation = database_incarnation_id()?;
892        self.ready_cardinality_counts_for_source(
893            ReadyCardinalitySource::Current {
894                database_incarnation: incarnation,
895            },
896            targets,
897            accepts,
898        )
899    }
900
901    fn ready_cardinality_counts_for_source(
902        &self,
903        source: ReadyCardinalitySource,
904        targets: ReadyCardinalityCountTargets<'_>,
905        accepts: impl FnOnce(&CardinalityBuildAuthority) -> bool,
906    ) -> Result<Option<Vec<u64>>, InternalError> {
907        let Some(journal) = self.journal else {
908            return Ok(None);
909        };
910        let Some(allocation) = self.cardinality_allocation else {
911            return Ok(None);
912        };
913        let (incarnation, accepted_root, watermark) = match source {
914            ReadyCardinalitySource::Current {
915                database_incarnation,
916            } => (
917                database_incarnation,
918                None,
919                journal.with_borrow(JournalTailStore::fold_watermark)?,
920            ),
921            ReadyCardinalitySource::Admitted {
922                database_incarnation,
923                accepted_root,
924                fold_watermark,
925            } => (database_incarnation, Some(accepted_root), fold_watermark),
926        };
927        self.with_schema(|schema| {
928            let (header, cursor) = schema.cardinality_generation_control()?;
929            let Some(header) = header else {
930                return Ok(None);
931            };
932            if header.state() != CardinalityGenerationState::Ready || cursor.is_some() {
933                return Ok(None);
934            }
935            let authority = match accepted_root {
936                Some(root) => CardinalityBuildAuthority::derive_for_admitted_consumer_root(
937                    schema,
938                    incarnation,
939                    allocation,
940                    root,
941                    watermark,
942                )?,
943                None => CardinalityBuildAuthority::derive_for_current_consumer(
944                    schema,
945                    incarnation,
946                    allocation,
947                    watermark,
948                )?,
949            };
950            let Some(authority) = authority else {
951                return Ok(None);
952            };
953            if !accepts(&authority) {
954                return Ok(None);
955            }
956            if header.validate_source(authority.source()).is_err() {
957                return Ok(None);
958            }
959            if targets.len() != 0 && schema.cardinality_count_slot_is_empty(header.slot())? {
960                return Ok(Some(vec![0; targets.len()]));
961            }
962            let counts = match targets {
963                ReadyCardinalityCountTargets::Digests(digests) => digests
964                    .iter()
965                    .map(|digest| {
966                        schema
967                            .cardinality_count(header.slot(), header.generation(), *digest)
968                            .map(|count| count.unwrap_or(0))
969                    })
970                    .collect::<Result<Vec<_>, _>>()?,
971                ReadyCardinalityCountTargets::UserIndexPrefixes(keys) => keys
972                    .iter()
973                    .map(|key| {
974                        let digest = CardinalityCountDigest::for_user_index_prefix(
975                            key.index_id(),
976                            key.prefix_components(),
977                        )?;
978                        schema
979                            .cardinality_count(header.slot(), header.generation(), digest)
980                            .map(|count| count.unwrap_or(0))
981                    })
982                    .collect::<Result<Vec<_>, _>>()?,
983            };
984            Ok(Some(counts))
985        })
986    }
987
988    /// Return the explicit lifecycle state of the bound index store.
989    #[must_use]
990    pub(in crate::db) fn index_state(&self) -> IndexState {
991        self.with_index(IndexStore::state)
992    }
993
994    /// Return the monotonic physical access-readiness revision.
995    pub(in crate::db) fn access_state_revision(&self) -> Result<u64, crate::error::InternalError> {
996        self.journal.map_or_else(
997            || Ok(self.with_index(IndexStore::access_state_revision)),
998            |journal| journal.with_borrow(JournalTailStore::access_state_revision),
999        )
1000    }
1001
1002    /// Mark the bound index store as Building.
1003    pub(in crate::db) fn mark_index_building(&self) -> Result<(), crate::error::InternalError> {
1004        self.set_index_state(IndexState::Building)
1005    }
1006
1007    /// Mark the bound index store as Ready.
1008    pub(in crate::db) fn mark_index_ready(&self) -> Result<(), crate::error::InternalError> {
1009        self.set_index_state(IndexState::Ready)
1010    }
1011
1012    fn set_index_state(&self, state: IndexState) -> Result<(), crate::error::InternalError> {
1013        if self.index_state() == state {
1014            return Ok(());
1015        }
1016        let revision = self.journal.map_or_else(
1017            || {
1018                self.with_index(IndexStore::access_state_revision)
1019                    .checked_add(1)
1020                    .ok_or_else(crate::error::InternalError::store_invariant)
1021            },
1022            |journal| journal.with_borrow_mut(JournalTailStore::advance_access_state_revision),
1023        )?;
1024        self.with_index_mut(|index| index.set_access_state(state, revision));
1025        Ok(())
1026    }
1027
1028    /// Return the raw row-store accessor.
1029    #[must_use]
1030    pub const fn data_store(&self) -> &'static LocalKey<RefCell<DataStore>> {
1031        self.data
1032    }
1033
1034    /// Return the raw index-store accessor.
1035    #[must_use]
1036    pub const fn index_store(&self) -> &'static LocalKey<RefCell<IndexStore>> {
1037        self.index
1038    }
1039
1040    /// Return the raw schema-store accessor.
1041    #[must_use]
1042    pub const fn schema_store(&self) -> &'static LocalKey<RefCell<SchemaStore>> {
1043        self.schema
1044    }
1045
1046    /// Return the raw journal-tail store accessor when this store is journaled.
1047    #[must_use]
1048    pub const fn journal_tail_store(&self) -> Option<&'static LocalKey<RefCell<JournalTailStore>>> {
1049        self.journal
1050    }
1051
1052    /// Return the data-memory allocation identity when generated wiring
1053    /// supplied it.
1054    #[must_use]
1055    pub const fn data_allocation(&self) -> Option<StoreAllocationIdentity> {
1056        self.allocations.data()
1057    }
1058
1059    /// Return the index-memory allocation identity when generated wiring
1060    /// supplied it.
1061    #[must_use]
1062    pub const fn index_allocation(&self) -> Option<StoreAllocationIdentity> {
1063        self.allocations.index()
1064    }
1065
1066    /// Return the schema-memory allocation identity when generated wiring
1067    /// supplied it.
1068    #[must_use]
1069    pub const fn schema_allocation(&self) -> Option<StoreAllocationIdentity> {
1070        self.allocations.schema()
1071    }
1072
1073    /// Return the journal-tail allocation identity when generated wiring
1074    /// supplied it.
1075    #[must_use]
1076    pub const fn journal_allocation(&self) -> Option<StoreAllocationIdentity> {
1077        self.allocations.journal()
1078    }
1079
1080    /// Return this store's complete allocation identity bundle.
1081    #[must_use]
1082    pub(in crate::db) const fn allocation_identities(&self) -> StoreAllocationIdentities {
1083        self.allocations
1084    }
1085
1086    /// Return this store's explicit runtime storage capabilities.
1087    #[must_use]
1088    pub const fn storage_capabilities(&self) -> StoreRuntimeStorageCapabilities {
1089        self.capabilities
1090    }
1091}
1092
1093fn apply_visible_cardinality_delta(base: u64, delta: i64) -> Option<u64> {
1094    if delta >= 0 {
1095        base.checked_add(u64::try_from(delta).ok()?)
1096    } else {
1097        base.checked_sub(delta.unsigned_abs())
1098    }
1099}
1100
1101fn checked_cardinality_sum(counts: &[u64]) -> Option<u64> {
1102    counts
1103        .iter()
1104        .try_fold(0_u64, |total, count| total.checked_add(*count))
1105}