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 sha2::{Digest, Sha256};
25use std::{cell::RefCell, thread::LocalKey};
26
27#[derive(Clone, Copy, Debug)]
37pub struct StoreHandle {
38 data: &'static LocalKey<RefCell<DataStore>>,
39 index: &'static LocalKey<RefCell<IndexStore>>,
40 schema: &'static LocalKey<RefCell<SchemaStore>>,
41 journal: Option<&'static LocalKey<RefCell<JournalTailStore>>>,
42 allocations: StoreAllocationIdentities,
43 cardinality_allocation: Option<CardinalityStoreAllocationIdentity>,
44 capabilities: StoreRuntimeStorageCapabilities,
45}
46
47enum ReadyCardinalityCountTargets<'a> {
48 Digests(&'a [CardinalityCountDigest]),
49 UserIndexPrefixes(&'a [UserIndexPrefixCardinalityKey]),
50}
51
52enum ReadyCardinalitySource {
53 Current {
54 database_incarnation: DatabaseIncarnationId,
55 },
56 Admitted {
57 database_incarnation: DatabaseIncarnationId,
58 accepted_root: CardinalityAcceptedRootIdentity,
59 fold_watermark: FoldWatermark,
60 },
61}
62
63#[derive(Clone, Copy, Debug, Eq, PartialEq)]
68pub(in crate::db) struct ExactPrefixCardinalityLifecycleStamp(
69 ExactPrefixCardinalityLifecycleIdentity,
70);
71
72#[derive(Clone, Copy, Debug, Eq, PartialEq)]
73enum ExactPrefixCardinalityLifecycleIdentity {
74 Volatile,
75 MissingDurableAuthority,
76 Corrupt,
77 Journaled {
78 header_digest: Option<[u8; 32]>,
79 cursor_present: bool,
80 delta_watermark: Option<FoldWatermark>,
81 },
82}
83
84#[derive(Clone, Debug, Eq, PartialEq)]
86pub(in crate::db) enum ExactUserIndexPrefixEvidence {
87 Exact(Vec<u64>),
88 Unavailable(ExactPrefixCardinalityLifecycleStamp),
89}
90
91impl ReadyCardinalityCountTargets<'_> {
92 const fn len(&self) -> usize {
93 match self {
94 Self::Digests(digests) => digests.len(),
95 Self::UserIndexPrefixes(keys) => keys.len(),
96 }
97 }
98}
99
100#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
104pub enum StoreRuntimeStorageMode {
105 #[default]
107 Heap,
108 Journaled,
110}
111
112impl StoreRuntimeStorageMode {
113 #[must_use]
115 pub const fn as_str(self) -> &'static str {
116 match self {
117 Self::Heap => "heap",
118 Self::Journaled => "journaled",
119 }
120 }
121}
122
123#[derive(CandidType, Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
125pub enum StoreAllocationIdentityCapability {
126 #[default]
128 Present,
129 Absent,
131}
132
133impl StoreAllocationIdentityCapability {
134 #[must_use]
136 pub const fn as_str(self) -> &'static str {
137 match self {
138 Self::Present => "present",
139 Self::Absent => "absent",
140 }
141 }
142}
143
144#[derive(CandidType, Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
146pub enum StoreDurability {
147 #[default]
149 Durable,
150 Volatile,
152}
153
154impl StoreDurability {
155 #[must_use]
157 pub const fn as_str(self) -> &'static str {
158 match self {
159 Self::Durable => "durable",
160 Self::Volatile => "volatile",
161 }
162 }
163}
164
165#[derive(CandidType, Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
167pub enum StoreRecoveryCapability {
168 #[default]
171 StableBasePlusJournalReplay,
172 None,
174}
175
176impl StoreRecoveryCapability {
177 #[must_use]
179 pub const fn as_str(self) -> &'static str {
180 match self {
181 Self::StableBasePlusJournalReplay => "stable-base-plus-journal-replay",
182 Self::None => "none",
183 }
184 }
185}
186
187#[derive(CandidType, Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
189pub enum StoreCommitParticipation {
190 #[default]
192 Durable,
193 LiveOnly,
195}
196
197impl StoreCommitParticipation {
198 #[must_use]
200 pub const fn as_str(self) -> &'static str {
201 match self {
202 Self::Durable => "durable",
203 Self::LiveOnly => "live-only",
204 }
205 }
206}
207
208#[derive(CandidType, Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
210pub enum StoreSchemaMetadataCapability {
211 LiveRebuiltMetadata,
214 #[default]
216 CanonicalStableHistoryPlusJournalTail,
217}
218
219impl StoreSchemaMetadataCapability {
220 #[must_use]
222 pub const fn as_str(self) -> &'static str {
223 match self {
224 Self::LiveRebuiltMetadata => "live-rebuilt-metadata",
225 Self::CanonicalStableHistoryPlusJournalTail => {
226 "canonical-stable-history-plus-journal-tail"
227 }
228 }
229 }
230}
231
232#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
234pub enum StoreRelationSourceCapability {
235 #[default]
237 DurableSource,
238 LiveSource,
240}
241
242#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
244pub enum StoreRelationTargetCapability {
245 #[default]
247 DurableTarget,
248 VolatileTarget,
250}
251
252#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
256pub struct StoreRuntimeStorageCapabilities {
257 storage_mode: StoreRuntimeStorageMode,
258 allocation_identity: StoreAllocationIdentityCapability,
259 durability: StoreDurability,
260 recovery: StoreRecoveryCapability,
261 commit_participation: StoreCommitParticipation,
262 schema_metadata: StoreSchemaMetadataCapability,
263 relation_source: StoreRelationSourceCapability,
264 relation_target: StoreRelationTargetCapability,
265}
266
267impl StoreRuntimeStorageCapabilities {
268 #[must_use]
270 pub const fn heap() -> Self {
271 Self {
272 storage_mode: StoreRuntimeStorageMode::Heap,
273 allocation_identity: StoreAllocationIdentityCapability::Absent,
274 durability: StoreDurability::Volatile,
275 recovery: StoreRecoveryCapability::None,
276 commit_participation: StoreCommitParticipation::LiveOnly,
277 schema_metadata: StoreSchemaMetadataCapability::LiveRebuiltMetadata,
278 relation_source: StoreRelationSourceCapability::LiveSource,
279 relation_target: StoreRelationTargetCapability::VolatileTarget,
280 }
281 }
282
283 #[must_use]
285 pub const fn journaled() -> Self {
286 Self {
287 storage_mode: StoreRuntimeStorageMode::Journaled,
288 allocation_identity: StoreAllocationIdentityCapability::Present,
289 durability: StoreDurability::Durable,
290 recovery: StoreRecoveryCapability::StableBasePlusJournalReplay,
291 commit_participation: StoreCommitParticipation::Durable,
292 schema_metadata: StoreSchemaMetadataCapability::CanonicalStableHistoryPlusJournalTail,
293 relation_source: StoreRelationSourceCapability::DurableSource,
294 relation_target: StoreRelationTargetCapability::DurableTarget,
295 }
296 }
297
298 #[must_use]
300 pub const fn storage_mode(self) -> StoreRuntimeStorageMode {
301 self.storage_mode
302 }
303
304 #[must_use]
306 pub const fn allocation_identity(self) -> StoreAllocationIdentityCapability {
307 self.allocation_identity
308 }
309
310 #[must_use]
312 pub const fn durability(self) -> StoreDurability {
313 self.durability
314 }
315
316 #[must_use]
318 pub const fn recovery(self) -> StoreRecoveryCapability {
319 self.recovery
320 }
321
322 #[must_use]
324 pub const fn commit_participation(self) -> StoreCommitParticipation {
325 self.commit_participation
326 }
327
328 #[must_use]
330 pub const fn schema_metadata(self) -> StoreSchemaMetadataCapability {
331 self.schema_metadata
332 }
333
334 #[must_use]
336 pub const fn relation_source(self) -> StoreRelationSourceCapability {
337 self.relation_source
338 }
339
340 #[must_use]
342 pub const fn relation_target(self) -> StoreRelationTargetCapability {
343 self.relation_target
344 }
345}
346
347#[derive(Clone, Copy, Debug, Eq, PartialEq)]
354pub struct StoreAllocationIdentity {
355 memory_id: u8,
356 stable_key: &'static str,
357}
358
359impl StoreAllocationIdentity {
360 #[must_use]
362 pub const fn new(memory_id: u8, stable_key: &'static str) -> Self {
363 Self {
364 memory_id,
365 stable_key,
366 }
367 }
368
369 #[must_use]
371 pub const fn memory_id(self) -> u8 {
372 self.memory_id
373 }
374
375 #[must_use]
377 pub const fn stable_key(self) -> &'static str {
378 self.stable_key
379 }
380}
381
382#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
390pub struct StoreAllocationIdentities {
391 data: Option<StoreAllocationIdentity>,
392 index: Option<StoreAllocationIdentity>,
393 schema: Option<StoreAllocationIdentity>,
394 journal: Option<StoreAllocationIdentity>,
395}
396
397impl StoreAllocationIdentities {
398 #[must_use]
400 pub const fn absent() -> Self {
401 Self {
402 data: None,
403 index: None,
404 schema: None,
405 journal: None,
406 }
407 }
408
409 #[must_use]
411 pub const fn new_journaled(
412 data: StoreAllocationIdentity,
413 index: StoreAllocationIdentity,
414 schema: StoreAllocationIdentity,
415 journal: StoreAllocationIdentity,
416 ) -> Self {
417 Self {
418 data: Some(data),
419 index: Some(index),
420 schema: Some(schema),
421 journal: Some(journal),
422 }
423 }
424
425 #[must_use]
427 pub const fn data(self) -> Option<StoreAllocationIdentity> {
428 self.data
429 }
430
431 #[must_use]
433 pub const fn index(self) -> Option<StoreAllocationIdentity> {
434 self.index
435 }
436
437 #[must_use]
439 pub const fn schema(self) -> Option<StoreAllocationIdentity> {
440 self.schema
441 }
442
443 #[must_use]
445 pub const fn journal(self) -> Option<StoreAllocationIdentity> {
446 self.journal
447 }
448
449 #[must_use]
452 pub const fn allocation_identity_capability(self) -> Option<StoreAllocationIdentityCapability> {
453 match (self.data, self.index, self.schema) {
454 (Some(_), Some(_), Some(_)) => Some(StoreAllocationIdentityCapability::Present),
455 (None, None, None) if self.journal.is_none() => {
456 Some(StoreAllocationIdentityCapability::Absent)
457 }
458 _ => None,
459 }
460 }
461
462 #[must_use]
465 pub const fn matches_storage_capabilities(
466 self,
467 capabilities: StoreRuntimeStorageCapabilities,
468 ) -> bool {
469 match capabilities.storage_mode() {
470 StoreRuntimeStorageMode::Heap => {
471 self.data.is_none()
472 && self.index.is_none()
473 && self.schema.is_none()
474 && self.journal.is_none()
475 }
476 StoreRuntimeStorageMode::Journaled => {
477 self.data.is_some()
478 && self.index.is_some()
479 && self.schema.is_some()
480 && self.journal.is_some()
481 }
482 }
483 }
484}
485
486impl StoreHandle {
487 #[must_use]
489 pub const fn new(
490 data: &'static LocalKey<RefCell<DataStore>>,
491 index: &'static LocalKey<RefCell<IndexStore>>,
492 schema: &'static LocalKey<RefCell<SchemaStore>>,
493 allocations: StoreAllocationIdentities,
494 capabilities: StoreRuntimeStorageCapabilities,
495 ) -> Self {
496 Self {
497 data,
498 index,
499 schema,
500 journal: None,
501 allocations,
502 cardinality_allocation: None,
503 capabilities,
504 }
505 }
506
507 #[must_use]
509 pub fn new_journaled(
510 data: &'static LocalKey<RefCell<DataStore>>,
511 index: &'static LocalKey<RefCell<IndexStore>>,
512 schema: &'static LocalKey<RefCell<SchemaStore>>,
513 journal: &'static LocalKey<RefCell<JournalTailStore>>,
514 allocations: StoreAllocationIdentities,
515 capabilities: StoreRuntimeStorageCapabilities,
516 ) -> Self {
517 let cardinality_allocation = CardinalityStoreAllocationIdentity::derive(allocations).ok();
518 Self {
519 data,
520 index,
521 schema,
522 journal: Some(journal),
523 allocations,
524 cardinality_allocation,
525 capabilities,
526 }
527 }
528
529 pub fn with_data<R>(&self, f: impl FnOnce(&DataStore) -> R) -> R {
531 self.data.with_borrow(f)
532 }
533
534 pub fn with_data_mut<R>(&self, f: impl FnOnce(&mut DataStore) -> R) -> R {
536 self.data.with_borrow_mut(f)
537 }
538
539 pub fn with_index<R>(&self, f: impl FnOnce(&IndexStore) -> R) -> R {
541 self.index.with_borrow(f)
542 }
543
544 pub fn with_index_mut<R>(&self, f: impl FnOnce(&mut IndexStore) -> R) -> R {
546 self.index.with_borrow_mut(f)
547 }
548
549 pub fn with_schema<R>(&self, f: impl FnOnce(&SchemaStore) -> R) -> R {
551 self.schema.with_borrow(f)
552 }
553
554 pub fn with_schema_mut<R>(&self, f: impl FnOnce(&mut SchemaStore) -> R) -> R {
556 self.schema.with_borrow_mut(f)
557 }
558
559 #[must_use]
561 pub(in crate::db) fn exact_entity_count(&self, entity: EntityTag) -> Option<u64> {
562 if self.journal.is_none() {
563 return self.with_data(|store| store.exact_entity_count(entity));
564 }
565 let delta = self.with_data(|store| store.exact_entity_cardinality_delta(entity))?;
566 let digest = CardinalityCountDigest::for_entity(entity);
567 let base = self
568 .ready_cardinality_counts(&[digest], |authority| authority.accepts_entity(entity))
569 .ok()
570 .flatten()?
571 .into_iter()
572 .next()?;
573 apply_visible_cardinality_delta(base, delta)
574 }
575
576 #[must_use]
578 pub(in crate::db) fn exact_user_index_prefix_count(
579 &self,
580 data_generation: u64,
581 key_kind: IndexKeyKind,
582 index_id: IndexId,
583 components: &[Vec<u8>],
584 ) -> Option<u64> {
585 self.exact_user_index_prefix_counts(data_generation, key_kind, index_id, [components])?
586 .into_iter()
587 .next()
588 }
589
590 #[must_use]
592 pub(in crate::db) fn exact_user_index_prefix_counts<'a>(
593 &self,
594 data_generation: u64,
595 key_kind: IndexKeyKind,
596 index_id: IndexId,
597 component_prefixes: impl IntoIterator<Item = &'a [Vec<u8>]>,
598 ) -> Option<Vec<u64>> {
599 let component_prefixes = component_prefixes.into_iter().collect::<Vec<_>>();
600 if key_kind != IndexKeyKind::User {
601 return None;
602 }
603 if self.journal.is_none() {
604 return self.with_index(|store| {
605 component_prefixes
606 .iter()
607 .map(|components| {
608 store.exact_prefix_cardinality(
609 data_generation,
610 key_kind,
611 index_id,
612 components,
613 )
614 })
615 .collect()
616 });
617 }
618 let deltas = self.with_index(|store| {
619 component_prefixes
620 .iter()
621 .map(|components| {
622 store.exact_prefix_cardinality_delta(key_kind, index_id, components)
623 })
624 .collect::<Option<Vec<_>>>()
625 })?;
626 let digests = component_prefixes
627 .iter()
628 .map(|components| {
629 CardinalityCountDigest::for_user_index_prefix(index_id, components).ok()
630 })
631 .collect::<Option<Vec<_>>>()?;
632 let bases = self
633 .ready_cardinality_counts(&digests, |authority| {
634 component_prefixes.iter().all(|components| {
635 authority.accepts_user_index_prefix(index_id, components.len())
636 })
637 })
638 .ok()
639 .flatten()?;
640 bases
641 .into_iter()
642 .zip(deltas)
643 .map(|(base, delta)| apply_visible_cardinality_delta(base, delta))
644 .collect()
645 }
646
647 #[must_use]
649 pub(in crate::db) fn exact_user_index_prefix_key_counts(
650 &self,
651 data_generation: u64,
652 keys: &[UserIndexPrefixCardinalityKey],
653 ) -> Option<Vec<u64>> {
654 self.exact_user_index_prefix_key_counts_with_authority(data_generation, keys, None)
655 }
656
657 #[must_use]
659 pub(in crate::db) fn exact_user_index_prefix_key_counts_for_admitted_root(
660 &self,
661 database_incarnation: DatabaseIncarnationId,
662 accepted_root: CardinalityAcceptedRootIdentity,
663 data_generation: u64,
664 keys: &[UserIndexPrefixCardinalityKey],
665 ) -> Option<Vec<u64>> {
666 self.exact_user_index_prefix_key_counts_with_authority(
667 data_generation,
668 keys,
669 Some((database_incarnation, accepted_root)),
670 )
671 }
672
673 #[must_use]
678 pub(in crate::db) fn exact_user_index_prefix_evidence_for_admitted_root(
679 &self,
680 database_incarnation: DatabaseIncarnationId,
681 accepted_root: CardinalityAcceptedRootIdentity,
682 keys: &[UserIndexPrefixCardinalityKey],
683 ) -> ExactUserIndexPrefixEvidence {
684 let data_generation = self.with_data(DataStore::generation);
685 if let Some(counts) = self.exact_user_index_prefix_key_counts_for_admitted_root(
686 database_incarnation,
687 accepted_root,
688 data_generation,
689 keys,
690 ) {
691 return ExactUserIndexPrefixEvidence::Exact(counts);
692 }
693
694 ExactUserIndexPrefixEvidence::Unavailable(
695 self.exact_user_index_prefix_evidence_lifecycle_stamp(),
696 )
697 }
698
699 #[must_use]
701 pub(in crate::db) fn exact_user_index_prefix_evidence_lifecycle_stamp(
702 &self,
703 ) -> ExactPrefixCardinalityLifecycleStamp {
704 if self.journal.is_none() {
705 return ExactPrefixCardinalityLifecycleStamp(
706 ExactPrefixCardinalityLifecycleIdentity::Volatile,
707 );
708 }
709 if self.cardinality_allocation.is_none() {
710 return ExactPrefixCardinalityLifecycleStamp(
711 ExactPrefixCardinalityLifecycleIdentity::MissingDurableAuthority,
712 );
713 }
714 let delta_watermark = self.with_index(IndexStore::exact_prefix_cardinality_delta_watermark);
715 match self.with_schema(SchemaStore::cardinality_generation_lifecycle_control) {
716 Ok((header, cursor_present)) => ExactPrefixCardinalityLifecycleStamp(
717 ExactPrefixCardinalityLifecycleIdentity::Journaled {
718 header_digest: header.map(|header| {
719 let mut hasher = Sha256::new();
720 hasher.update(b"icydb.cardinality-lifecycle-stamp.v1");
721 hasher.update(header.encode());
722 hasher.finalize().into()
723 }),
724 cursor_present,
725 delta_watermark,
726 },
727 ),
728 Err(_) => ExactPrefixCardinalityLifecycleStamp(
729 ExactPrefixCardinalityLifecycleIdentity::Corrupt,
730 ),
731 }
732 }
733
734 fn exact_user_index_prefix_key_counts_with_authority(
735 &self,
736 data_generation: u64,
737 keys: &[UserIndexPrefixCardinalityKey],
738 admitted: Option<(DatabaseIncarnationId, CardinalityAcceptedRootIdentity)>,
739 ) -> Option<Vec<u64>> {
740 if keys.is_empty() {
741 return None;
742 }
743 if self.journal.is_none() {
744 return self.with_index(|store| {
745 keys.iter()
746 .map(|key| {
747 store.exact_prefix_cardinality(
748 data_generation,
749 IndexKeyKind::User,
750 key.index_id(),
751 key.prefix_components(),
752 )
753 })
754 .collect()
755 });
756 }
757 let (delta_watermark, deltas) = self.with_index(|store| {
758 let watermark = store.exact_prefix_cardinality_delta_watermark()?;
759 keys.iter()
760 .map(|key| {
761 store.exact_prefix_cardinality_delta(
762 IndexKeyKind::User,
763 key.index_id(),
764 key.prefix_components(),
765 )
766 })
767 .collect::<Option<Vec<_>>>()
768 .map(|deltas| (watermark, deltas))
769 })?;
770 let accepts = |authority: &CardinalityBuildAuthority| {
771 keys.iter().all(|key| {
772 authority.accepts_user_index_prefix(key.index_id(), key.prefix_components().len())
773 })
774 };
775 let bases = match admitted {
776 Some((database_incarnation, accepted_root)) => self
777 .ready_cardinality_counts_for_source(
778 ReadyCardinalitySource::Admitted {
779 database_incarnation,
780 accepted_root,
781 fold_watermark: delta_watermark,
782 },
783 ReadyCardinalityCountTargets::UserIndexPrefixes(keys),
784 accepts,
785 ),
786 None => self.ready_cardinality_counts_for_targets(
787 ReadyCardinalityCountTargets::UserIndexPrefixes(keys),
788 accepts,
789 ),
790 }
791 .ok()
792 .flatten()?;
793 bases
794 .into_iter()
795 .zip(deltas)
796 .map(|(base, delta)| apply_visible_cardinality_delta(base, delta))
797 .collect()
798 }
799
800 #[must_use]
806 pub(in crate::db) fn user_index_prefix_family_has_ready_generation<'a, I>(
807 &self,
808 database_incarnation: DatabaseIncarnationId,
809 accepted_root: CardinalityAcceptedRootIdentity,
810 data_generation: u64,
811 key_kind: IndexKeyKind,
812 index_id: IndexId,
813 component_prefixes: I,
814 ) -> bool
815 where
816 I: Clone + IntoIterator<Item = &'a [Vec<u8>]>,
817 {
818 if key_kind != IndexKeyKind::User || component_prefixes.clone().into_iter().next().is_none()
819 {
820 return false;
821 }
822 if self.journal.is_none() {
823 return self.with_index(|store| {
824 component_prefixes.clone().into_iter().all(|components| {
825 store
826 .exact_prefix_cardinality(data_generation, key_kind, index_id, components)
827 .is_some()
828 })
829 });
830 }
831 let delta_watermark = self.with_index(|store| {
832 let watermark = store.exact_prefix_cardinality_delta_watermark()?;
833 component_prefixes
834 .clone()
835 .into_iter()
836 .all(|components| {
837 store
838 .exact_prefix_cardinality_delta(key_kind, index_id, components)
839 .is_some()
840 })
841 .then_some(watermark)
842 });
843 delta_watermark.is_some_and(|watermark| {
844 self.ready_cardinality_counts_for_source(
845 ReadyCardinalitySource::Admitted {
846 database_incarnation,
847 accepted_root,
848 fold_watermark: watermark,
849 },
850 ReadyCardinalityCountTargets::Digests(&[]),
851 |authority| {
852 component_prefixes.into_iter().all(|components| {
853 authority.accepts_user_index_prefix(index_id, components.len())
854 })
855 },
856 )
857 .is_ok_and(|counts| counts.is_some())
858 })
859 }
860
861 #[must_use]
863 pub(in crate::db) fn exact_user_index_prefix_count_sum<'a>(
864 &self,
865 data_generation: u64,
866 key_kind: IndexKeyKind,
867 index_id: IndexId,
868 component_prefixes: impl IntoIterator<Item = &'a [Vec<u8>]>,
869 stop_after: Option<u64>,
870 ) -> Option<u64> {
871 let component_prefixes = component_prefixes.into_iter().collect::<Vec<_>>();
872 if self.journal.is_none() {
873 return self.with_index(|store| {
874 store.exact_prefix_cardinality_sum(
875 data_generation,
876 key_kind,
877 index_id,
878 component_prefixes.iter().copied(),
879 stop_after,
880 )
881 });
882 }
883 let counts = self.exact_user_index_prefix_counts(
884 data_generation,
885 key_kind,
886 index_id,
887 component_prefixes.iter().copied(),
888 )?;
889 let mut total = 0_u64;
890 for count in counts {
891 total = total.checked_add(count)?;
892 if stop_after.is_some_and(|required| total >= required) {
893 break;
894 }
895 }
896 Some(total)
897 }
898
899 #[must_use]
901 pub(in crate::db) fn exact_user_index_child_prefixes_for_parent_set<'a>(
902 &self,
903 data_generation: u64,
904 index_id: IndexId,
905 parent_prefixes: impl IntoIterator<Item = &'a [Vec<u8>]>,
906 total_cap: usize,
907 ) -> Option<Vec<Vec<Vec<u8>>>> {
908 let mut parent_prefixes = parent_prefixes
909 .into_iter()
910 .map(<[Vec<u8>]>::to_vec)
911 .collect::<Vec<_>>();
912 if parent_prefixes.iter().any(Vec::is_empty) {
913 return None;
914 }
915 icydb_schema::compact_sort_unstable_by(&mut parent_prefixes, Ord::cmp);
916 parent_prefixes.dedup();
917 let child_prefixes = self.with_index(|store| {
918 store.exact_child_prefixes_for_parent_set(
919 data_generation,
920 IndexKeyKind::User,
921 index_id,
922 parent_prefixes.iter().map(Vec::as_slice),
923 total_cap,
924 )
925 })?;
926 if self.journal.is_none() {
927 return Some(child_prefixes);
928 }
929 let parent_count = parent_prefixes.len();
930 let counts = self.exact_user_index_prefix_counts(
931 data_generation,
932 IndexKeyKind::User,
933 index_id,
934 parent_prefixes
935 .iter()
936 .chain(&child_prefixes)
937 .map(Vec::as_slice),
938 )?;
939 let (parent_counts, child_counts) = counts.split_at(parent_count);
940 let parent_total = checked_cardinality_sum(parent_counts)?;
941 let child_total = checked_cardinality_sum(child_counts)?;
942 (parent_total == child_total).then_some(child_prefixes)
943 }
944
945 fn ready_cardinality_counts(
946 &self,
947 digests: &[CardinalityCountDigest],
948 accepts: impl FnOnce(&CardinalityBuildAuthority) -> bool,
949 ) -> Result<Option<Vec<u64>>, InternalError> {
950 self.ready_cardinality_counts_for_targets(
951 ReadyCardinalityCountTargets::Digests(digests),
952 accepts,
953 )
954 }
955
956 fn ready_cardinality_counts_for_targets(
957 &self,
958 targets: ReadyCardinalityCountTargets<'_>,
959 accepts: impl FnOnce(&CardinalityBuildAuthority) -> bool,
960 ) -> Result<Option<Vec<u64>>, InternalError> {
961 let incarnation = database_incarnation_id()?;
962 self.ready_cardinality_counts_for_source(
963 ReadyCardinalitySource::Current {
964 database_incarnation: incarnation,
965 },
966 targets,
967 accepts,
968 )
969 }
970
971 fn ready_cardinality_counts_for_source(
972 &self,
973 source: ReadyCardinalitySource,
974 targets: ReadyCardinalityCountTargets<'_>,
975 accepts: impl FnOnce(&CardinalityBuildAuthority) -> bool,
976 ) -> Result<Option<Vec<u64>>, InternalError> {
977 let Some(journal) = self.journal else {
978 return Ok(None);
979 };
980 let Some(allocation) = self.cardinality_allocation else {
981 return Ok(None);
982 };
983 let (incarnation, accepted_root, watermark) = match source {
984 ReadyCardinalitySource::Current {
985 database_incarnation,
986 } => (
987 database_incarnation,
988 None,
989 journal.with_borrow(JournalTailStore::fold_watermark)?,
990 ),
991 ReadyCardinalitySource::Admitted {
992 database_incarnation,
993 accepted_root,
994 fold_watermark,
995 } => (database_incarnation, Some(accepted_root), fold_watermark),
996 };
997 self.with_schema(|schema| {
998 let (header, cursor) = schema.cardinality_generation_control()?;
999 let Some(header) = header else {
1000 return Ok(None);
1001 };
1002 if header.state() != CardinalityGenerationState::Ready || cursor.is_some() {
1003 return Ok(None);
1004 }
1005 let authority = match accepted_root {
1006 Some(root) => CardinalityBuildAuthority::derive_for_admitted_consumer_root(
1007 schema,
1008 incarnation,
1009 allocation,
1010 root,
1011 watermark,
1012 )?,
1013 None => CardinalityBuildAuthority::derive_for_current_consumer(
1014 schema,
1015 incarnation,
1016 allocation,
1017 watermark,
1018 )?,
1019 };
1020 let Some(authority) = authority else {
1021 return Ok(None);
1022 };
1023 if !accepts(&authority) {
1024 return Ok(None);
1025 }
1026 if header.validate_source(authority.source()).is_err() {
1027 return Ok(None);
1028 }
1029 if targets.len() != 0 && schema.cardinality_count_slot_is_empty(header.slot())? {
1030 return Ok(Some(vec![0; targets.len()]));
1031 }
1032 let counts = match targets {
1033 ReadyCardinalityCountTargets::Digests(digests) => digests
1034 .iter()
1035 .map(|digest| {
1036 schema
1037 .cardinality_count(header.slot(), header.generation(), *digest)
1038 .map(|count| count.unwrap_or(0))
1039 })
1040 .collect::<Result<Vec<_>, _>>()?,
1041 ReadyCardinalityCountTargets::UserIndexPrefixes(keys) => keys
1042 .iter()
1043 .map(|key| {
1044 let digest = CardinalityCountDigest::for_user_index_prefix(
1045 key.index_id(),
1046 key.prefix_components(),
1047 )?;
1048 schema
1049 .cardinality_count(header.slot(), header.generation(), digest)
1050 .map(|count| count.unwrap_or(0))
1051 })
1052 .collect::<Result<Vec<_>, _>>()?,
1053 };
1054 Ok(Some(counts))
1055 })
1056 }
1057
1058 #[must_use]
1060 pub(in crate::db) fn index_state(&self) -> IndexState {
1061 self.with_index(IndexStore::state)
1062 }
1063
1064 pub(in crate::db) fn access_state_revision(&self) -> Result<u64, crate::error::InternalError> {
1066 self.journal.map_or_else(
1067 || Ok(self.with_index(IndexStore::access_state_revision)),
1068 |journal| journal.with_borrow(JournalTailStore::access_state_revision),
1069 )
1070 }
1071
1072 pub(in crate::db) fn mark_index_building(&self) -> Result<(), crate::error::InternalError> {
1074 self.set_index_state(IndexState::Building)
1075 }
1076
1077 pub(in crate::db) fn mark_index_ready(&self) -> Result<(), crate::error::InternalError> {
1079 self.set_index_state(IndexState::Ready)
1080 }
1081
1082 fn set_index_state(&self, state: IndexState) -> Result<(), crate::error::InternalError> {
1083 if self.index_state() == state {
1084 return Ok(());
1085 }
1086 let revision = self.journal.map_or_else(
1087 || {
1088 self.with_index(IndexStore::access_state_revision)
1089 .checked_add(1)
1090 .ok_or_else(crate::error::InternalError::store_invariant)
1091 },
1092 |journal| journal.with_borrow_mut(JournalTailStore::advance_access_state_revision),
1093 )?;
1094 self.with_index_mut(|index| index.set_access_state(state, revision));
1095 Ok(())
1096 }
1097
1098 #[must_use]
1100 pub const fn data_store(&self) -> &'static LocalKey<RefCell<DataStore>> {
1101 self.data
1102 }
1103
1104 #[must_use]
1106 pub const fn index_store(&self) -> &'static LocalKey<RefCell<IndexStore>> {
1107 self.index
1108 }
1109
1110 #[must_use]
1112 pub const fn schema_store(&self) -> &'static LocalKey<RefCell<SchemaStore>> {
1113 self.schema
1114 }
1115
1116 #[must_use]
1118 pub const fn journal_tail_store(&self) -> Option<&'static LocalKey<RefCell<JournalTailStore>>> {
1119 self.journal
1120 }
1121
1122 #[must_use]
1125 pub const fn data_allocation(&self) -> Option<StoreAllocationIdentity> {
1126 self.allocations.data()
1127 }
1128
1129 #[must_use]
1132 pub const fn index_allocation(&self) -> Option<StoreAllocationIdentity> {
1133 self.allocations.index()
1134 }
1135
1136 #[must_use]
1139 pub const fn schema_allocation(&self) -> Option<StoreAllocationIdentity> {
1140 self.allocations.schema()
1141 }
1142
1143 #[must_use]
1146 pub const fn journal_allocation(&self) -> Option<StoreAllocationIdentity> {
1147 self.allocations.journal()
1148 }
1149
1150 #[must_use]
1152 pub(in crate::db) const fn allocation_identities(&self) -> StoreAllocationIdentities {
1153 self.allocations
1154 }
1155
1156 #[must_use]
1158 pub const fn storage_capabilities(&self) -> StoreRuntimeStorageCapabilities {
1159 self.capabilities
1160 }
1161}
1162
1163fn apply_visible_cardinality_delta(base: u64, delta: i64) -> Option<u64> {
1164 if delta >= 0 {
1165 base.checked_add(u64::try_from(delta).ok()?)
1166 } else {
1167 base.checked_sub(delta.unsigned_abs())
1168 }
1169}
1170
1171fn checked_cardinality_sum(counts: &[u64]) -> Option<u64> {
1172 counts
1173 .iter()
1174 .try_fold(0_u64, |total, count| total.checked_add(*count))
1175}
1176
1177crate::retained::retained_copy!(ExactPrefixCardinalityLifecycleStamp);