1use 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#[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#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
75pub enum StoreRuntimeStorageMode {
76 #[default]
78 Heap,
79 Journaled,
81}
82
83impl StoreRuntimeStorageMode {
84 #[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#[derive(CandidType, Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
96pub enum StoreAllocationIdentityCapability {
97 #[default]
99 Present,
100 Absent,
102}
103
104impl StoreAllocationIdentityCapability {
105 #[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#[derive(CandidType, Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
117pub enum StoreDurability {
118 #[default]
120 Durable,
121 Volatile,
123}
124
125impl StoreDurability {
126 #[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#[derive(CandidType, Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
138pub enum StoreRecoveryCapability {
139 #[default]
142 StableBasePlusJournalReplay,
143 None,
145}
146
147impl StoreRecoveryCapability {
148 #[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#[derive(CandidType, Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
160pub enum StoreCommitParticipation {
161 #[default]
163 Durable,
164 LiveOnly,
166}
167
168impl StoreCommitParticipation {
169 #[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#[derive(CandidType, Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
181pub enum StoreSchemaMetadataCapability {
182 LiveRebuiltMetadata,
185 #[default]
187 CanonicalStableHistoryPlusJournalTail,
188}
189
190impl StoreSchemaMetadataCapability {
191 #[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#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
205pub enum StoreRelationSourceCapability {
206 #[default]
208 DurableSource,
209 LiveSource,
211}
212
213#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
215pub enum StoreRelationTargetCapability {
216 #[default]
218 DurableTarget,
219 VolatileTarget,
221}
222
223#[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 #[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 #[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 #[must_use]
271 pub const fn storage_mode(self) -> StoreRuntimeStorageMode {
272 self.storage_mode
273 }
274
275 #[must_use]
277 pub const fn allocation_identity(self) -> StoreAllocationIdentityCapability {
278 self.allocation_identity
279 }
280
281 #[must_use]
283 pub const fn durability(self) -> StoreDurability {
284 self.durability
285 }
286
287 #[must_use]
289 pub const fn recovery(self) -> StoreRecoveryCapability {
290 self.recovery
291 }
292
293 #[must_use]
295 pub const fn commit_participation(self) -> StoreCommitParticipation {
296 self.commit_participation
297 }
298
299 #[must_use]
301 pub const fn schema_metadata(self) -> StoreSchemaMetadataCapability {
302 self.schema_metadata
303 }
304
305 #[must_use]
307 pub const fn relation_source(self) -> StoreRelationSourceCapability {
308 self.relation_source
309 }
310
311 #[must_use]
313 pub const fn relation_target(self) -> StoreRelationTargetCapability {
314 self.relation_target
315 }
316}
317
318#[derive(Clone, Copy, Debug, Eq, PartialEq)]
325pub struct StoreAllocationIdentity {
326 memory_id: u8,
327 stable_key: &'static str,
328}
329
330impl StoreAllocationIdentity {
331 #[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 #[must_use]
342 pub const fn memory_id(self) -> u8 {
343 self.memory_id
344 }
345
346 #[must_use]
348 pub const fn stable_key(self) -> &'static str {
349 self.stable_key
350 }
351}
352
353#[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 #[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 #[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 #[must_use]
398 pub const fn data(self) -> Option<StoreAllocationIdentity> {
399 self.data
400 }
401
402 #[must_use]
404 pub const fn index(self) -> Option<StoreAllocationIdentity> {
405 self.index
406 }
407
408 #[must_use]
410 pub const fn schema(self) -> Option<StoreAllocationIdentity> {
411 self.schema
412 }
413
414 #[must_use]
416 pub const fn journal(self) -> Option<StoreAllocationIdentity> {
417 self.journal
418 }
419
420 #[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 #[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 #[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 #[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 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 pub fn with_data_mut<R>(&self, f: impl FnOnce(&mut DataStore) -> R) -> R {
517 self.data.with_borrow_mut(f)
518 }
519
520 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 pub fn with_index_mut<R>(&self, f: impl FnOnce(&mut IndexStore) -> R) -> R {
537 self.index.with_borrow_mut(f)
538 }
539
540 pub fn with_schema<R>(&self, f: impl FnOnce(&SchemaStore) -> R) -> R {
542 self.schema.with_borrow(f)
543 }
544
545 pub fn with_schema_mut<R>(&self, f: impl FnOnce(&mut SchemaStore) -> R) -> R {
547 self.schema.with_borrow_mut(f)
548 }
549
550 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[must_use]
990 pub(in crate::db) fn index_state(&self) -> IndexState {
991 self.with_index(IndexStore::state)
992 }
993
994 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 pub(in crate::db) fn mark_index_building(&self) -> Result<(), crate::error::InternalError> {
1004 self.set_index_state(IndexState::Building)
1005 }
1006
1007 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 #[must_use]
1030 pub const fn data_store(&self) -> &'static LocalKey<RefCell<DataStore>> {
1031 self.data
1032 }
1033
1034 #[must_use]
1036 pub const fn index_store(&self) -> &'static LocalKey<RefCell<IndexStore>> {
1037 self.index
1038 }
1039
1040 #[must_use]
1042 pub const fn schema_store(&self) -> &'static LocalKey<RefCell<SchemaStore>> {
1043 self.schema
1044 }
1045
1046 #[must_use]
1048 pub const fn journal_tail_store(&self) -> Option<&'static LocalKey<RefCell<JournalTailStore>>> {
1049 self.journal
1050 }
1051
1052 #[must_use]
1055 pub const fn data_allocation(&self) -> Option<StoreAllocationIdentity> {
1056 self.allocations.data()
1057 }
1058
1059 #[must_use]
1062 pub const fn index_allocation(&self) -> Option<StoreAllocationIdentity> {
1063 self.allocations.index()
1064 }
1065
1066 #[must_use]
1069 pub const fn schema_allocation(&self) -> Option<StoreAllocationIdentity> {
1070 self.allocations.schema()
1071 }
1072
1073 #[must_use]
1076 pub const fn journal_allocation(&self) -> Option<StoreAllocationIdentity> {
1077 self.allocations.journal()
1078 }
1079
1080 #[must_use]
1082 pub(in crate::db) const fn allocation_identities(&self) -> StoreAllocationIdentities {
1083 self.allocations
1084 }
1085
1086 #[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}