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 pub fn from_committed_key(
362 stable_key: &'static str,
363 ) -> Result<Self, ic_memory::RuntimeOpenError> {
364 Ok(Self::new(
365 crate::memory::committed_memory_id(stable_key)?,
366 stable_key,
367 ))
368 }
369
370 #[must_use]
372 pub const fn new(memory_id: u8, stable_key: &'static str) -> Self {
373 Self {
374 memory_id,
375 stable_key,
376 }
377 }
378
379 #[must_use]
381 pub const fn memory_id(self) -> u8 {
382 self.memory_id
383 }
384
385 #[must_use]
387 pub const fn stable_key(self) -> &'static str {
388 self.stable_key
389 }
390}
391
392#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
400pub struct StoreAllocationIdentities {
401 data: Option<StoreAllocationIdentity>,
402 index: Option<StoreAllocationIdentity>,
403 schema: Option<StoreAllocationIdentity>,
404 journal: Option<StoreAllocationIdentity>,
405}
406
407impl StoreAllocationIdentities {
408 #[must_use]
410 pub const fn absent() -> Self {
411 Self {
412 data: None,
413 index: None,
414 schema: None,
415 journal: None,
416 }
417 }
418
419 #[must_use]
421 pub const fn new_journaled(
422 data: StoreAllocationIdentity,
423 index: StoreAllocationIdentity,
424 schema: StoreAllocationIdentity,
425 journal: StoreAllocationIdentity,
426 ) -> Self {
427 Self {
428 data: Some(data),
429 index: Some(index),
430 schema: Some(schema),
431 journal: Some(journal),
432 }
433 }
434
435 #[must_use]
437 pub const fn data(self) -> Option<StoreAllocationIdentity> {
438 self.data
439 }
440
441 #[must_use]
443 pub const fn index(self) -> Option<StoreAllocationIdentity> {
444 self.index
445 }
446
447 #[must_use]
449 pub const fn schema(self) -> Option<StoreAllocationIdentity> {
450 self.schema
451 }
452
453 #[must_use]
455 pub const fn journal(self) -> Option<StoreAllocationIdentity> {
456 self.journal
457 }
458
459 #[must_use]
462 pub const fn allocation_identity_capability(self) -> Option<StoreAllocationIdentityCapability> {
463 match (self.data, self.index, self.schema) {
464 (Some(_), Some(_), Some(_)) => Some(StoreAllocationIdentityCapability::Present),
465 (None, None, None) if self.journal.is_none() => {
466 Some(StoreAllocationIdentityCapability::Absent)
467 }
468 _ => None,
469 }
470 }
471
472 #[must_use]
475 pub const fn matches_storage_capabilities(
476 self,
477 capabilities: StoreRuntimeStorageCapabilities,
478 ) -> bool {
479 match capabilities.storage_mode() {
480 StoreRuntimeStorageMode::Heap => {
481 self.data.is_none()
482 && self.index.is_none()
483 && self.schema.is_none()
484 && self.journal.is_none()
485 }
486 StoreRuntimeStorageMode::Journaled => {
487 self.data.is_some()
488 && self.index.is_some()
489 && self.schema.is_some()
490 && self.journal.is_some()
491 }
492 }
493 }
494}
495
496impl StoreHandle {
497 #[must_use]
499 pub const fn new(
500 data: &'static LocalKey<RefCell<DataStore>>,
501 index: &'static LocalKey<RefCell<IndexStore>>,
502 schema: &'static LocalKey<RefCell<SchemaStore>>,
503 allocations: StoreAllocationIdentities,
504 capabilities: StoreRuntimeStorageCapabilities,
505 ) -> Self {
506 Self {
507 data,
508 index,
509 schema,
510 journal: None,
511 allocations,
512 cardinality_allocation: None,
513 capabilities,
514 }
515 }
516
517 #[must_use]
519 pub fn new_journaled(
520 data: &'static LocalKey<RefCell<DataStore>>,
521 index: &'static LocalKey<RefCell<IndexStore>>,
522 schema: &'static LocalKey<RefCell<SchemaStore>>,
523 journal: &'static LocalKey<RefCell<JournalTailStore>>,
524 allocations: StoreAllocationIdentities,
525 capabilities: StoreRuntimeStorageCapabilities,
526 ) -> Self {
527 let cardinality_allocation = CardinalityStoreAllocationIdentity::derive(allocations).ok();
528 Self {
529 data,
530 index,
531 schema,
532 journal: Some(journal),
533 allocations,
534 cardinality_allocation,
535 capabilities,
536 }
537 }
538
539 pub fn with_data<R>(&self, f: impl FnOnce(&DataStore) -> R) -> R {
541 self.data.with_borrow(f)
542 }
543
544 pub fn with_data_mut<R>(&self, f: impl FnOnce(&mut DataStore) -> R) -> R {
546 self.data.with_borrow_mut(f)
547 }
548
549 pub fn with_index<R>(&self, f: impl FnOnce(&IndexStore) -> R) -> R {
551 self.index.with_borrow(f)
552 }
553
554 pub fn with_index_mut<R>(&self, f: impl FnOnce(&mut IndexStore) -> R) -> R {
556 self.index.with_borrow_mut(f)
557 }
558
559 pub fn with_schema<R>(&self, f: impl FnOnce(&SchemaStore) -> R) -> R {
561 self.schema.with_borrow(f)
562 }
563
564 pub fn with_schema_mut<R>(&self, f: impl FnOnce(&mut SchemaStore) -> R) -> R {
566 self.schema.with_borrow_mut(f)
567 }
568
569 #[must_use]
571 pub(in crate::db) fn exact_entity_count(&self, entity: EntityTag) -> Option<u64> {
572 if self.journal.is_none() {
573 return self.with_data(|store| store.exact_entity_count(entity));
574 }
575 let delta = self.with_data(|store| store.exact_entity_cardinality_delta(entity))?;
576 let digest = CardinalityCountDigest::for_entity(entity);
577 let base = self
578 .ready_cardinality_counts(&[digest], |authority| authority.accepts_entity(entity))
579 .ok()
580 .flatten()?
581 .into_iter()
582 .next()?;
583 apply_visible_cardinality_delta(base, delta)
584 }
585
586 #[must_use]
588 pub(in crate::db) fn exact_user_index_prefix_count(
589 &self,
590 data_generation: u64,
591 key_kind: IndexKeyKind,
592 index_id: IndexId,
593 components: &[Vec<u8>],
594 ) -> Option<u64> {
595 self.exact_user_index_prefix_counts(data_generation, key_kind, index_id, [components])?
596 .into_iter()
597 .next()
598 }
599
600 #[must_use]
602 pub(in crate::db) fn exact_user_index_prefix_counts<'a>(
603 &self,
604 data_generation: u64,
605 key_kind: IndexKeyKind,
606 index_id: IndexId,
607 component_prefixes: impl IntoIterator<Item = &'a [Vec<u8>]>,
608 ) -> Option<Vec<u64>> {
609 let component_prefixes = component_prefixes.into_iter().collect::<Vec<_>>();
610 if key_kind != IndexKeyKind::User {
611 return None;
612 }
613 if self.journal.is_none() {
614 return self.with_index(|store| {
615 component_prefixes
616 .iter()
617 .map(|components| {
618 store.exact_prefix_cardinality(
619 data_generation,
620 key_kind,
621 index_id,
622 components,
623 )
624 })
625 .collect()
626 });
627 }
628 let deltas = self.with_index(|store| {
629 component_prefixes
630 .iter()
631 .map(|components| {
632 store.exact_prefix_cardinality_delta(key_kind, index_id, components)
633 })
634 .collect::<Option<Vec<_>>>()
635 })?;
636 let digests = component_prefixes
637 .iter()
638 .map(|components| {
639 CardinalityCountDigest::for_user_index_prefix(index_id, components).ok()
640 })
641 .collect::<Option<Vec<_>>>()?;
642 let bases = self
643 .ready_cardinality_counts(&digests, |authority| {
644 component_prefixes.iter().all(|components| {
645 authority.accepts_user_index_prefix(index_id, components.len())
646 })
647 })
648 .ok()
649 .flatten()?;
650 bases
651 .into_iter()
652 .zip(deltas)
653 .map(|(base, delta)| apply_visible_cardinality_delta(base, delta))
654 .collect()
655 }
656
657 #[must_use]
659 pub(in crate::db) fn exact_user_index_prefix_key_counts(
660 &self,
661 data_generation: u64,
662 keys: &[UserIndexPrefixCardinalityKey],
663 ) -> Option<Vec<u64>> {
664 self.exact_user_index_prefix_key_counts_with_authority(data_generation, keys, None)
665 }
666
667 #[must_use]
669 pub(in crate::db) fn exact_user_index_prefix_key_counts_for_admitted_root(
670 &self,
671 database_incarnation: DatabaseIncarnationId,
672 accepted_root: CardinalityAcceptedRootIdentity,
673 data_generation: u64,
674 keys: &[UserIndexPrefixCardinalityKey],
675 ) -> Option<Vec<u64>> {
676 self.exact_user_index_prefix_key_counts_with_authority(
677 data_generation,
678 keys,
679 Some((database_incarnation, accepted_root)),
680 )
681 }
682
683 #[must_use]
688 pub(in crate::db) fn exact_user_index_prefix_evidence_for_admitted_root(
689 &self,
690 database_incarnation: DatabaseIncarnationId,
691 accepted_root: CardinalityAcceptedRootIdentity,
692 keys: &[UserIndexPrefixCardinalityKey],
693 ) -> ExactUserIndexPrefixEvidence {
694 let data_generation = self.with_data(DataStore::generation);
695 if let Some(counts) = self.exact_user_index_prefix_key_counts_for_admitted_root(
696 database_incarnation,
697 accepted_root,
698 data_generation,
699 keys,
700 ) {
701 return ExactUserIndexPrefixEvidence::Exact(counts);
702 }
703
704 ExactUserIndexPrefixEvidence::Unavailable(
705 self.exact_user_index_prefix_evidence_lifecycle_stamp(),
706 )
707 }
708
709 #[must_use]
711 pub(in crate::db) fn exact_user_index_prefix_evidence_lifecycle_stamp(
712 &self,
713 ) -> ExactPrefixCardinalityLifecycleStamp {
714 if self.journal.is_none() {
715 return ExactPrefixCardinalityLifecycleStamp(
716 ExactPrefixCardinalityLifecycleIdentity::Volatile,
717 );
718 }
719 if self.cardinality_allocation.is_none() {
720 return ExactPrefixCardinalityLifecycleStamp(
721 ExactPrefixCardinalityLifecycleIdentity::MissingDurableAuthority,
722 );
723 }
724 let delta_watermark = self.with_index(IndexStore::exact_prefix_cardinality_delta_watermark);
725 match self.with_schema(SchemaStore::cardinality_generation_lifecycle_control) {
726 Ok((header, cursor_present)) => ExactPrefixCardinalityLifecycleStamp(
727 ExactPrefixCardinalityLifecycleIdentity::Journaled {
728 header_digest: header.map(|header| {
729 let mut hasher = Sha256::new();
730 hasher.update(b"icydb.cardinality-lifecycle-stamp.v1");
731 hasher.update(header.encode());
732 hasher.finalize().into()
733 }),
734 cursor_present,
735 delta_watermark,
736 },
737 ),
738 Err(_) => ExactPrefixCardinalityLifecycleStamp(
739 ExactPrefixCardinalityLifecycleIdentity::Corrupt,
740 ),
741 }
742 }
743
744 fn exact_user_index_prefix_key_counts_with_authority(
745 &self,
746 data_generation: u64,
747 keys: &[UserIndexPrefixCardinalityKey],
748 admitted: Option<(DatabaseIncarnationId, CardinalityAcceptedRootIdentity)>,
749 ) -> Option<Vec<u64>> {
750 if keys.is_empty() {
751 return None;
752 }
753 if self.journal.is_none() {
754 return self.with_index(|store| {
755 keys.iter()
756 .map(|key| {
757 store.exact_prefix_cardinality(
758 data_generation,
759 IndexKeyKind::User,
760 key.index_id(),
761 key.prefix_components(),
762 )
763 })
764 .collect()
765 });
766 }
767 let (delta_watermark, deltas) = self.with_index(|store| {
768 let watermark = store.exact_prefix_cardinality_delta_watermark()?;
769 keys.iter()
770 .map(|key| {
771 store.exact_prefix_cardinality_delta(
772 IndexKeyKind::User,
773 key.index_id(),
774 key.prefix_components(),
775 )
776 })
777 .collect::<Option<Vec<_>>>()
778 .map(|deltas| (watermark, deltas))
779 })?;
780 let accepts = |authority: &CardinalityBuildAuthority| {
781 keys.iter().all(|key| {
782 authority.accepts_user_index_prefix(key.index_id(), key.prefix_components().len())
783 })
784 };
785 let bases = match admitted {
786 Some((database_incarnation, accepted_root)) => self
787 .ready_cardinality_counts_for_source(
788 ReadyCardinalitySource::Admitted {
789 database_incarnation,
790 accepted_root,
791 fold_watermark: delta_watermark,
792 },
793 ReadyCardinalityCountTargets::UserIndexPrefixes(keys),
794 accepts,
795 ),
796 None => self.ready_cardinality_counts_for_targets(
797 ReadyCardinalityCountTargets::UserIndexPrefixes(keys),
798 accepts,
799 ),
800 }
801 .ok()
802 .flatten()?;
803 bases
804 .into_iter()
805 .zip(deltas)
806 .map(|(base, delta)| apply_visible_cardinality_delta(base, delta))
807 .collect()
808 }
809
810 #[must_use]
816 pub(in crate::db) fn user_index_prefix_family_has_ready_generation<'a, I>(
817 &self,
818 database_incarnation: DatabaseIncarnationId,
819 accepted_root: CardinalityAcceptedRootIdentity,
820 data_generation: u64,
821 key_kind: IndexKeyKind,
822 index_id: IndexId,
823 component_prefixes: I,
824 ) -> bool
825 where
826 I: Clone + IntoIterator<Item = &'a [Vec<u8>]>,
827 {
828 if key_kind != IndexKeyKind::User || component_prefixes.clone().into_iter().next().is_none()
829 {
830 return false;
831 }
832 if self.journal.is_none() {
833 return self.with_index(|store| {
834 component_prefixes.clone().into_iter().all(|components| {
835 store
836 .exact_prefix_cardinality(data_generation, key_kind, index_id, components)
837 .is_some()
838 })
839 });
840 }
841 let delta_watermark = self.with_index(|store| {
842 let watermark = store.exact_prefix_cardinality_delta_watermark()?;
843 component_prefixes
844 .clone()
845 .into_iter()
846 .all(|components| {
847 store
848 .exact_prefix_cardinality_delta(key_kind, index_id, components)
849 .is_some()
850 })
851 .then_some(watermark)
852 });
853 delta_watermark.is_some_and(|watermark| {
854 self.ready_cardinality_counts_for_source(
855 ReadyCardinalitySource::Admitted {
856 database_incarnation,
857 accepted_root,
858 fold_watermark: watermark,
859 },
860 ReadyCardinalityCountTargets::Digests(&[]),
861 |authority| {
862 component_prefixes.into_iter().all(|components| {
863 authority.accepts_user_index_prefix(index_id, components.len())
864 })
865 },
866 )
867 .is_ok_and(|counts| counts.is_some())
868 })
869 }
870
871 #[must_use]
873 pub(in crate::db) fn exact_user_index_prefix_count_sum<'a>(
874 &self,
875 data_generation: u64,
876 key_kind: IndexKeyKind,
877 index_id: IndexId,
878 component_prefixes: impl IntoIterator<Item = &'a [Vec<u8>]>,
879 stop_after: Option<u64>,
880 ) -> Option<u64> {
881 let component_prefixes = component_prefixes.into_iter().collect::<Vec<_>>();
882 if self.journal.is_none() {
883 return self.with_index(|store| {
884 store.exact_prefix_cardinality_sum(
885 data_generation,
886 key_kind,
887 index_id,
888 component_prefixes.iter().copied(),
889 stop_after,
890 )
891 });
892 }
893 let counts = self.exact_user_index_prefix_counts(
894 data_generation,
895 key_kind,
896 index_id,
897 component_prefixes.iter().copied(),
898 )?;
899 let mut total = 0_u64;
900 for count in counts {
901 total = total.checked_add(count)?;
902 if stop_after.is_some_and(|required| total >= required) {
903 break;
904 }
905 }
906 Some(total)
907 }
908
909 #[must_use]
911 pub(in crate::db) fn exact_user_index_child_prefixes_for_parent_set<'a>(
912 &self,
913 data_generation: u64,
914 index_id: IndexId,
915 parent_prefixes: impl IntoIterator<Item = &'a [Vec<u8>]>,
916 total_cap: usize,
917 ) -> Option<Vec<Vec<Vec<u8>>>> {
918 let mut parent_prefixes = parent_prefixes
919 .into_iter()
920 .map(<[Vec<u8>]>::to_vec)
921 .collect::<Vec<_>>();
922 if parent_prefixes.iter().any(Vec::is_empty) {
923 return None;
924 }
925 icydb_schema::compact_sort_unstable_by(&mut parent_prefixes, Ord::cmp);
926 parent_prefixes.dedup();
927 let child_prefixes = self.with_index(|store| {
928 store.exact_child_prefixes_for_parent_set(
929 data_generation,
930 IndexKeyKind::User,
931 index_id,
932 parent_prefixes.iter().map(Vec::as_slice),
933 total_cap,
934 )
935 })?;
936 if self.journal.is_none() {
937 return Some(child_prefixes);
938 }
939 let parent_count = parent_prefixes.len();
940 let counts = self.exact_user_index_prefix_counts(
941 data_generation,
942 IndexKeyKind::User,
943 index_id,
944 parent_prefixes
945 .iter()
946 .chain(&child_prefixes)
947 .map(Vec::as_slice),
948 )?;
949 let (parent_counts, child_counts) = counts.split_at(parent_count);
950 let parent_total = checked_cardinality_sum(parent_counts)?;
951 let child_total = checked_cardinality_sum(child_counts)?;
952 (parent_total == child_total).then_some(child_prefixes)
953 }
954
955 fn ready_cardinality_counts(
956 &self,
957 digests: &[CardinalityCountDigest],
958 accepts: impl FnOnce(&CardinalityBuildAuthority) -> bool,
959 ) -> Result<Option<Vec<u64>>, InternalError> {
960 self.ready_cardinality_counts_for_targets(
961 ReadyCardinalityCountTargets::Digests(digests),
962 accepts,
963 )
964 }
965
966 fn ready_cardinality_counts_for_targets(
967 &self,
968 targets: ReadyCardinalityCountTargets<'_>,
969 accepts: impl FnOnce(&CardinalityBuildAuthority) -> bool,
970 ) -> Result<Option<Vec<u64>>, InternalError> {
971 let incarnation = database_incarnation_id()?;
972 self.ready_cardinality_counts_for_source(
973 ReadyCardinalitySource::Current {
974 database_incarnation: incarnation,
975 },
976 targets,
977 accepts,
978 )
979 }
980
981 fn ready_cardinality_counts_for_source(
982 &self,
983 source: ReadyCardinalitySource,
984 targets: ReadyCardinalityCountTargets<'_>,
985 accepts: impl FnOnce(&CardinalityBuildAuthority) -> bool,
986 ) -> Result<Option<Vec<u64>>, InternalError> {
987 let Some(journal) = self.journal else {
988 return Ok(None);
989 };
990 let Some(allocation) = self.cardinality_allocation else {
991 return Ok(None);
992 };
993 let (incarnation, accepted_root, watermark) = match source {
994 ReadyCardinalitySource::Current {
995 database_incarnation,
996 } => (
997 database_incarnation,
998 None,
999 journal.with_borrow(JournalTailStore::fold_watermark)?,
1000 ),
1001 ReadyCardinalitySource::Admitted {
1002 database_incarnation,
1003 accepted_root,
1004 fold_watermark,
1005 } => (database_incarnation, Some(accepted_root), fold_watermark),
1006 };
1007 self.with_schema(|schema| {
1008 let (header, cursor) = schema.cardinality_generation_control()?;
1009 let Some(header) = header else {
1010 return Ok(None);
1011 };
1012 if header.state() != CardinalityGenerationState::Ready || cursor.is_some() {
1013 return Ok(None);
1014 }
1015 let authority = match accepted_root {
1016 Some(root) => CardinalityBuildAuthority::derive_for_admitted_consumer_root(
1017 schema,
1018 incarnation,
1019 allocation,
1020 root,
1021 watermark,
1022 )?,
1023 None => CardinalityBuildAuthority::derive_for_current_consumer(
1024 schema,
1025 incarnation,
1026 allocation,
1027 watermark,
1028 )?,
1029 };
1030 let Some(authority) = authority else {
1031 return Ok(None);
1032 };
1033 if !accepts(&authority) {
1034 return Ok(None);
1035 }
1036 if header.validate_source(authority.source()).is_err() {
1037 return Ok(None);
1038 }
1039 if targets.len() != 0 && schema.cardinality_count_slot_is_empty(header.slot())? {
1040 return Ok(Some(vec![0; targets.len()]));
1041 }
1042 let counts = match targets {
1043 ReadyCardinalityCountTargets::Digests(digests) => digests
1044 .iter()
1045 .map(|digest| {
1046 schema
1047 .cardinality_count(header.slot(), header.generation(), *digest)
1048 .map(|count| count.unwrap_or(0))
1049 })
1050 .collect::<Result<Vec<_>, _>>()?,
1051 ReadyCardinalityCountTargets::UserIndexPrefixes(keys) => keys
1052 .iter()
1053 .map(|key| {
1054 let digest = CardinalityCountDigest::for_user_index_prefix(
1055 key.index_id(),
1056 key.prefix_components(),
1057 )?;
1058 schema
1059 .cardinality_count(header.slot(), header.generation(), digest)
1060 .map(|count| count.unwrap_or(0))
1061 })
1062 .collect::<Result<Vec<_>, _>>()?,
1063 };
1064 Ok(Some(counts))
1065 })
1066 }
1067
1068 #[must_use]
1070 pub(in crate::db) fn index_state(&self) -> IndexState {
1071 self.with_index(IndexStore::state)
1072 }
1073
1074 pub(in crate::db) fn access_state_revision(&self) -> Result<u64, crate::error::InternalError> {
1076 self.journal.map_or_else(
1077 || Ok(self.with_index(IndexStore::access_state_revision)),
1078 |journal| journal.with_borrow(JournalTailStore::access_state_revision),
1079 )
1080 }
1081
1082 pub(in crate::db) fn mark_index_building(&self) -> Result<(), crate::error::InternalError> {
1084 self.set_index_state(IndexState::Building)
1085 }
1086
1087 pub(in crate::db) fn mark_index_ready(&self) -> Result<(), crate::error::InternalError> {
1089 self.set_index_state(IndexState::Ready)
1090 }
1091
1092 fn set_index_state(&self, state: IndexState) -> Result<(), crate::error::InternalError> {
1093 if self.index_state() == state {
1094 return Ok(());
1095 }
1096 let revision = self.journal.map_or_else(
1097 || {
1098 self.with_index(IndexStore::access_state_revision)
1099 .checked_add(1)
1100 .ok_or_else(crate::error::InternalError::store_invariant)
1101 },
1102 |journal| journal.with_borrow_mut(JournalTailStore::advance_access_state_revision),
1103 )?;
1104 self.with_index_mut(|index| index.set_access_state(state, revision));
1105 Ok(())
1106 }
1107
1108 #[must_use]
1110 pub const fn data_store(&self) -> &'static LocalKey<RefCell<DataStore>> {
1111 self.data
1112 }
1113
1114 #[must_use]
1116 pub const fn index_store(&self) -> &'static LocalKey<RefCell<IndexStore>> {
1117 self.index
1118 }
1119
1120 #[must_use]
1122 pub const fn schema_store(&self) -> &'static LocalKey<RefCell<SchemaStore>> {
1123 self.schema
1124 }
1125
1126 #[must_use]
1128 pub const fn journal_tail_store(&self) -> Option<&'static LocalKey<RefCell<JournalTailStore>>> {
1129 self.journal
1130 }
1131
1132 #[must_use]
1135 pub const fn data_allocation(&self) -> Option<StoreAllocationIdentity> {
1136 self.allocations.data()
1137 }
1138
1139 #[must_use]
1142 pub const fn index_allocation(&self) -> Option<StoreAllocationIdentity> {
1143 self.allocations.index()
1144 }
1145
1146 #[must_use]
1149 pub const fn schema_allocation(&self) -> Option<StoreAllocationIdentity> {
1150 self.allocations.schema()
1151 }
1152
1153 #[must_use]
1156 pub const fn journal_allocation(&self) -> Option<StoreAllocationIdentity> {
1157 self.allocations.journal()
1158 }
1159
1160 #[must_use]
1162 pub(in crate::db) const fn allocation_identities(&self) -> StoreAllocationIdentities {
1163 self.allocations
1164 }
1165
1166 #[must_use]
1168 pub const fn storage_capabilities(&self) -> StoreRuntimeStorageCapabilities {
1169 self.capabilities
1170 }
1171}
1172
1173fn apply_visible_cardinality_delta(base: u64, delta: i64) -> Option<u64> {
1174 if delta >= 0 {
1175 base.checked_add(u64::try_from(delta).ok()?)
1176 } else {
1177 base.checked_sub(delta.unsigned_abs())
1178 }
1179}
1180
1181fn checked_cardinality_sum(counts: &[u64]) -> Option<u64> {
1182 counts
1183 .iter()
1184 .try_fold(0_u64, |total, count| total.checked_add(*count))
1185}
1186
1187crate::retained::retained_copy!(ExactPrefixCardinalityLifecycleStamp);