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