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