use crate::db::{
index::IndexState,
registry::{
StoreAllocationIdentityCapability, StoreCommitParticipation, StoreDurability,
StoreRecoveryCapability, StoreRuntimeStorageCapabilities, StoreSchemaMetadataCapability,
},
};
use candid::CandidType;
use serde::Deserialize;
#[cfg_attr(doc, doc = "StorageReport\n\nLive storage snapshot payload.")]
#[derive(CandidType, Clone, Debug, Default, Deserialize)]
pub struct StorageReport {
pub(crate) storage_data: Vec<DataStoreSnapshot>,
pub(crate) storage_index: Vec<IndexStoreSnapshot>,
pub(crate) schema_storage: Vec<SchemaStoreSnapshot>,
pub(crate) entity_storage: Vec<EntitySnapshot>,
pub(crate) corrupted_keys: u64,
pub(crate) corrupted_entries: u64,
}
#[cfg_attr(
doc,
doc = "IntegrityTotals\n\nAggregated integrity-scan counters across all stores."
)]
#[derive(CandidType, Clone, Debug, Default, Deserialize)]
pub struct IntegrityTotals {
pub(crate) data_rows_scanned: u64,
pub(crate) index_entries_scanned: u64,
pub(crate) corrupted_data_keys: u64,
pub(crate) corrupted_data_rows: u64,
pub(crate) corrupted_index_keys: u64,
pub(crate) corrupted_index_entries: u64,
pub(crate) missing_index_entries: u64,
pub(crate) divergent_index_entries: u64,
pub(crate) orphan_index_references: u64,
pub(crate) misuse_findings: u64,
}
impl IntegrityTotals {
pub(super) const fn add_store_snapshot(&mut self, store: &IntegrityStoreSnapshot) {
self.data_rows_scanned = self
.data_rows_scanned
.saturating_add(store.data_rows_scanned);
self.index_entries_scanned = self
.index_entries_scanned
.saturating_add(store.index_entries_scanned);
self.corrupted_data_keys = self
.corrupted_data_keys
.saturating_add(store.corrupted_data_keys);
self.corrupted_data_rows = self
.corrupted_data_rows
.saturating_add(store.corrupted_data_rows);
self.corrupted_index_keys = self
.corrupted_index_keys
.saturating_add(store.corrupted_index_keys);
self.corrupted_index_entries = self
.corrupted_index_entries
.saturating_add(store.corrupted_index_entries);
self.missing_index_entries = self
.missing_index_entries
.saturating_add(store.missing_index_entries);
self.divergent_index_entries = self
.divergent_index_entries
.saturating_add(store.divergent_index_entries);
self.orphan_index_references = self
.orphan_index_references
.saturating_add(store.orphan_index_references);
self.misuse_findings = self.misuse_findings.saturating_add(store.misuse_findings);
}
#[must_use]
pub const fn data_rows_scanned(&self) -> u64 {
self.data_rows_scanned
}
#[must_use]
pub const fn index_entries_scanned(&self) -> u64 {
self.index_entries_scanned
}
#[must_use]
pub const fn corrupted_data_keys(&self) -> u64 {
self.corrupted_data_keys
}
#[must_use]
pub const fn corrupted_data_rows(&self) -> u64 {
self.corrupted_data_rows
}
#[must_use]
pub const fn corrupted_index_keys(&self) -> u64 {
self.corrupted_index_keys
}
#[must_use]
pub const fn corrupted_index_entries(&self) -> u64 {
self.corrupted_index_entries
}
#[must_use]
pub const fn missing_index_entries(&self) -> u64 {
self.missing_index_entries
}
#[must_use]
pub const fn divergent_index_entries(&self) -> u64 {
self.divergent_index_entries
}
#[must_use]
pub const fn orphan_index_references(&self) -> u64 {
self.orphan_index_references
}
#[must_use]
pub const fn misuse_findings(&self) -> u64 {
self.misuse_findings
}
}
#[cfg_attr(
doc,
doc = "IntegrityStoreSnapshot\n\nPer-store integrity findings and scan counters."
)]
#[derive(CandidType, Clone, Debug, Default, Deserialize)]
pub struct IntegrityStoreSnapshot {
pub(crate) path: String,
pub(crate) data_rows_scanned: u64,
pub(crate) index_entries_scanned: u64,
pub(crate) corrupted_data_keys: u64,
pub(crate) corrupted_data_rows: u64,
pub(crate) corrupted_index_keys: u64,
pub(crate) corrupted_index_entries: u64,
pub(crate) missing_index_entries: u64,
pub(crate) divergent_index_entries: u64,
pub(crate) orphan_index_references: u64,
pub(crate) misuse_findings: u64,
}
impl IntegrityStoreSnapshot {
#[must_use]
pub(crate) fn new(path: String) -> Self {
Self {
path,
..Self::default()
}
}
#[must_use]
pub const fn path(&self) -> &str {
self.path.as_str()
}
#[must_use]
pub const fn data_rows_scanned(&self) -> u64 {
self.data_rows_scanned
}
#[must_use]
pub const fn index_entries_scanned(&self) -> u64 {
self.index_entries_scanned
}
#[must_use]
pub const fn corrupted_data_keys(&self) -> u64 {
self.corrupted_data_keys
}
#[must_use]
pub const fn corrupted_data_rows(&self) -> u64 {
self.corrupted_data_rows
}
#[must_use]
pub const fn corrupted_index_keys(&self) -> u64 {
self.corrupted_index_keys
}
#[must_use]
pub const fn corrupted_index_entries(&self) -> u64 {
self.corrupted_index_entries
}
#[must_use]
pub const fn missing_index_entries(&self) -> u64 {
self.missing_index_entries
}
#[must_use]
pub const fn divergent_index_entries(&self) -> u64 {
self.divergent_index_entries
}
#[must_use]
pub const fn orphan_index_references(&self) -> u64 {
self.orphan_index_references
}
#[must_use]
pub const fn misuse_findings(&self) -> u64 {
self.misuse_findings
}
}
#[cfg_attr(
doc,
doc = "IntegrityReport\n\nFull integrity-scan output across all registered stores."
)]
#[derive(CandidType, Clone, Debug, Default, Deserialize)]
pub struct IntegrityReport {
pub(crate) stores: Vec<IntegrityStoreSnapshot>,
pub(crate) totals: IntegrityTotals,
}
impl IntegrityReport {
#[must_use]
pub(crate) const fn new(stores: Vec<IntegrityStoreSnapshot>, totals: IntegrityTotals) -> Self {
Self { stores, totals }
}
#[must_use]
pub const fn stores(&self) -> &[IntegrityStoreSnapshot] {
self.stores.as_slice()
}
#[must_use]
pub const fn totals(&self) -> &IntegrityTotals {
&self.totals
}
}
impl StorageReport {
#[must_use]
pub(crate) const fn new(
storage_data: Vec<DataStoreSnapshot>,
storage_index: Vec<IndexStoreSnapshot>,
schema_storage: Vec<SchemaStoreSnapshot>,
entity_storage: Vec<EntitySnapshot>,
corrupted_keys: u64,
corrupted_entries: u64,
) -> Self {
Self {
storage_data,
storage_index,
schema_storage,
entity_storage,
corrupted_keys,
corrupted_entries,
}
}
#[must_use]
pub const fn storage_data(&self) -> &[DataStoreSnapshot] {
self.storage_data.as_slice()
}
#[must_use]
pub const fn storage_index(&self) -> &[IndexStoreSnapshot] {
self.storage_index.as_slice()
}
#[must_use]
pub const fn schema_storage(&self) -> &[SchemaStoreSnapshot] {
self.schema_storage.as_slice()
}
#[must_use]
pub const fn entity_storage(&self) -> &[EntitySnapshot] {
self.entity_storage.as_slice()
}
#[must_use]
pub const fn corrupted_keys(&self) -> u64 {
self.corrupted_keys
}
#[must_use]
pub const fn corrupted_entries(&self) -> u64 {
self.corrupted_entries
}
}
#[cfg_attr(doc, doc = "SchemaStoreSnapshot\n\nSchema-store diagnostic row.")]
#[derive(CandidType, Clone, Debug, Default, Deserialize)]
pub struct SchemaStoreSnapshot {
pub(crate) path: String,
pub(crate) storage: StoreSnapshotStorageMode,
pub(crate) allocation: StoreAllocationIdentityCapability,
pub(crate) durability: StoreDurability,
pub(crate) commit: StoreCommitParticipation,
pub(crate) recovery: StoreRecoveryCapability,
pub(crate) schema_metadata: StoreSchemaMetadataCapability,
pub(crate) memory_id: Option<u8>,
pub(crate) stable_key: Option<String>,
pub(crate) schema_version: Option<u32>,
pub(crate) schema_fingerprint: Option<String>,
pub(crate) entity_count: u64,
}
#[derive(CandidType, Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
pub enum StoreSnapshotStorageMode {
#[default]
Stable,
Heap,
Journaled,
}
impl StoreSnapshotStorageMode {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Stable => "stable",
Self::Heap => "heap",
Self::Journaled => "journaled",
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct StoreSnapshotAllocationIdentity {
memory_id: u8,
stable_key: String,
}
impl StoreSnapshotAllocationIdentity {
pub(crate) const fn new(memory_id: u8, stable_key: String) -> Self {
Self {
memory_id,
stable_key,
}
}
const fn memory_id(&self) -> u8 {
self.memory_id
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub(crate) struct StoreSnapshotSchemaMetadata {
schema_version: Option<u32>,
schema_fingerprint: Option<String>,
}
impl StoreSnapshotSchemaMetadata {
pub(crate) const fn absent() -> Self {
Self {
schema_version: None,
schema_fingerprint: None,
}
}
pub(crate) const fn new(schema_version: u32, schema_fingerprint: String) -> Self {
Self {
schema_version: Some(schema_version),
schema_fingerprint: Some(schema_fingerprint),
}
}
const fn schema_version(&self) -> Option<u32> {
self.schema_version
}
fn schema_fingerprint(&self) -> Option<String> {
self.schema_fingerprint.clone()
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub(crate) struct IndexStoreSnapshotStats {
entries: u64,
user_entries: u64,
system_entries: u64,
memory_bytes: u64,
state: IndexState,
}
impl IndexStoreSnapshotStats {
pub(crate) const fn new(
entries: u64,
user_entries: u64,
system_entries: u64,
memory_bytes: u64,
state: IndexState,
) -> Self {
Self {
entries,
user_entries,
system_entries,
memory_bytes,
state,
}
}
}
impl SchemaStoreSnapshot {
#[must_use]
pub(crate) fn new(
path: String,
storage: StoreSnapshotStorageMode,
capabilities: StoreRuntimeStorageCapabilities,
allocation: Option<StoreSnapshotAllocationIdentity>,
schema_metadata: StoreSnapshotSchemaMetadata,
entity_count: u64,
) -> Self {
let memory_id = allocation
.as_ref()
.map(StoreSnapshotAllocationIdentity::memory_id);
let stable_key = match allocation {
Some(allocation) => Some(allocation.stable_key),
None => None,
};
Self {
path,
storage,
allocation: capabilities.allocation_identity(),
durability: capabilities.durability(),
commit: capabilities.commit_participation(),
recovery: capabilities.recovery(),
schema_metadata: capabilities.schema_metadata(),
memory_id,
stable_key,
schema_version: schema_metadata.schema_version(),
schema_fingerprint: schema_metadata.schema_fingerprint(),
entity_count,
}
}
#[must_use]
pub const fn path(&self) -> &str {
self.path.as_str()
}
#[must_use]
pub const fn storage(&self) -> StoreSnapshotStorageMode {
self.storage
}
#[must_use]
pub const fn allocation(&self) -> StoreAllocationIdentityCapability {
self.allocation
}
#[must_use]
pub const fn durability(&self) -> StoreDurability {
self.durability
}
#[must_use]
pub const fn commit(&self) -> StoreCommitParticipation {
self.commit
}
#[must_use]
pub const fn recovery(&self) -> StoreRecoveryCapability {
self.recovery
}
#[must_use]
pub const fn schema_metadata(&self) -> StoreSchemaMetadataCapability {
self.schema_metadata
}
#[must_use]
pub const fn memory_id(&self) -> Option<u8> {
self.memory_id
}
#[must_use]
pub const fn stable_key(&self) -> Option<&str> {
match &self.stable_key {
Some(value) => Some(value.as_str()),
None => None,
}
}
#[must_use]
pub const fn schema_version(&self) -> Option<u32> {
self.schema_version
}
#[must_use]
pub const fn schema_fingerprint(&self) -> Option<&str> {
match &self.schema_fingerprint {
Some(value) => Some(value.as_str()),
None => None,
}
}
#[must_use]
pub const fn entity_count(&self) -> u64 {
self.entity_count
}
}
#[cfg_attr(doc, doc = "DataStoreSnapshot\n\nData-store snapshot row.")]
#[derive(CandidType, Clone, Debug, Default, Deserialize)]
pub struct DataStoreSnapshot {
pub(crate) path: String,
pub(crate) storage: StoreSnapshotStorageMode,
pub(crate) allocation: StoreAllocationIdentityCapability,
pub(crate) durability: StoreDurability,
pub(crate) commit: StoreCommitParticipation,
pub(crate) recovery: StoreRecoveryCapability,
pub(crate) schema_metadata: StoreSchemaMetadataCapability,
pub(crate) memory_id: Option<u8>,
pub(crate) stable_key: Option<String>,
pub(crate) schema_version: Option<u32>,
pub(crate) schema_fingerprint: Option<String>,
pub(crate) entries: u64,
pub(crate) memory_bytes: u64,
}
impl DataStoreSnapshot {
#[must_use]
pub(crate) fn new(
path: String,
storage: StoreSnapshotStorageMode,
capabilities: StoreRuntimeStorageCapabilities,
allocation: Option<StoreSnapshotAllocationIdentity>,
schema_metadata: StoreSnapshotSchemaMetadata,
entries: u64,
memory_bytes: u64,
) -> Self {
let memory_id = allocation
.as_ref()
.map(StoreSnapshotAllocationIdentity::memory_id);
let stable_key = match allocation {
Some(allocation) => Some(allocation.stable_key),
None => None,
};
Self {
path,
storage,
allocation: capabilities.allocation_identity(),
durability: capabilities.durability(),
commit: capabilities.commit_participation(),
recovery: capabilities.recovery(),
schema_metadata: capabilities.schema_metadata(),
memory_id,
stable_key,
schema_version: schema_metadata.schema_version(),
schema_fingerprint: schema_metadata.schema_fingerprint(),
entries,
memory_bytes,
}
}
#[must_use]
pub const fn path(&self) -> &str {
self.path.as_str()
}
#[must_use]
pub const fn storage(&self) -> StoreSnapshotStorageMode {
self.storage
}
#[must_use]
pub const fn allocation(&self) -> StoreAllocationIdentityCapability {
self.allocation
}
#[must_use]
pub const fn durability(&self) -> StoreDurability {
self.durability
}
#[must_use]
pub const fn commit(&self) -> StoreCommitParticipation {
self.commit
}
#[must_use]
pub const fn recovery(&self) -> StoreRecoveryCapability {
self.recovery
}
#[must_use]
pub const fn schema_metadata(&self) -> StoreSchemaMetadataCapability {
self.schema_metadata
}
#[must_use]
pub const fn memory_id(&self) -> Option<u8> {
self.memory_id
}
#[must_use]
pub const fn stable_key(&self) -> Option<&str> {
match &self.stable_key {
Some(value) => Some(value.as_str()),
None => None,
}
}
#[must_use]
pub const fn schema_version(&self) -> Option<u32> {
self.schema_version
}
#[must_use]
pub const fn schema_fingerprint(&self) -> Option<&str> {
match &self.schema_fingerprint {
Some(value) => Some(value.as_str()),
None => None,
}
}
#[must_use]
pub const fn entries(&self) -> u64 {
self.entries
}
#[must_use]
pub const fn memory_bytes(&self) -> u64 {
self.memory_bytes
}
}
#[cfg_attr(doc, doc = "IndexStoreSnapshot\n\nIndex-store snapshot row.")]
#[derive(CandidType, Clone, Debug, Default, Deserialize)]
pub struct IndexStoreSnapshot {
pub(crate) path: String,
pub(crate) storage: StoreSnapshotStorageMode,
pub(crate) allocation: StoreAllocationIdentityCapability,
pub(crate) durability: StoreDurability,
pub(crate) commit: StoreCommitParticipation,
pub(crate) recovery: StoreRecoveryCapability,
pub(crate) schema_metadata: StoreSchemaMetadataCapability,
pub(crate) memory_id: Option<u8>,
pub(crate) stable_key: Option<String>,
pub(crate) schema_version: Option<u32>,
pub(crate) schema_fingerprint: Option<String>,
pub(crate) entries: u64,
pub(crate) user_entries: u64,
pub(crate) system_entries: u64,
pub(crate) memory_bytes: u64,
pub(crate) state: IndexState,
}
impl IndexStoreSnapshot {
#[must_use]
pub(crate) fn new(
path: String,
storage: StoreSnapshotStorageMode,
capabilities: StoreRuntimeStorageCapabilities,
allocation: Option<StoreSnapshotAllocationIdentity>,
schema_metadata: StoreSnapshotSchemaMetadata,
stats: IndexStoreSnapshotStats,
) -> Self {
let memory_id = allocation
.as_ref()
.map(StoreSnapshotAllocationIdentity::memory_id);
let stable_key = match allocation {
Some(allocation) => Some(allocation.stable_key),
None => None,
};
Self {
path,
storage,
allocation: capabilities.allocation_identity(),
durability: capabilities.durability(),
commit: capabilities.commit_participation(),
recovery: capabilities.recovery(),
schema_metadata: capabilities.schema_metadata(),
memory_id,
stable_key,
schema_version: schema_metadata.schema_version(),
schema_fingerprint: schema_metadata.schema_fingerprint(),
entries: stats.entries,
user_entries: stats.user_entries,
system_entries: stats.system_entries,
memory_bytes: stats.memory_bytes,
state: stats.state,
}
}
#[must_use]
pub const fn path(&self) -> &str {
self.path.as_str()
}
#[must_use]
pub const fn storage(&self) -> StoreSnapshotStorageMode {
self.storage
}
#[must_use]
pub const fn allocation(&self) -> StoreAllocationIdentityCapability {
self.allocation
}
#[must_use]
pub const fn durability(&self) -> StoreDurability {
self.durability
}
#[must_use]
pub const fn commit(&self) -> StoreCommitParticipation {
self.commit
}
#[must_use]
pub const fn recovery(&self) -> StoreRecoveryCapability {
self.recovery
}
#[must_use]
pub const fn schema_metadata(&self) -> StoreSchemaMetadataCapability {
self.schema_metadata
}
#[must_use]
pub const fn memory_id(&self) -> Option<u8> {
self.memory_id
}
#[must_use]
pub const fn stable_key(&self) -> Option<&str> {
match &self.stable_key {
Some(value) => Some(value.as_str()),
None => None,
}
}
#[must_use]
pub const fn schema_version(&self) -> Option<u32> {
self.schema_version
}
#[must_use]
pub const fn schema_fingerprint(&self) -> Option<&str> {
match &self.schema_fingerprint {
Some(value) => Some(value.as_str()),
None => None,
}
}
#[must_use]
pub const fn entries(&self) -> u64 {
self.entries
}
#[must_use]
pub const fn user_entries(&self) -> u64 {
self.user_entries
}
#[must_use]
pub const fn system_entries(&self) -> u64 {
self.system_entries
}
#[must_use]
pub const fn memory_bytes(&self) -> u64 {
self.memory_bytes
}
#[must_use]
pub const fn state(&self) -> IndexState {
self.state
}
}
#[cfg_attr(doc, doc = "EntitySnapshot\n\nPer-entity storage snapshot row.")]
#[derive(CandidType, Clone, Debug, Default, Deserialize)]
pub struct EntitySnapshot {
pub(crate) store: String,
pub(crate) path: String,
pub(crate) entries: u64,
pub(crate) memory_bytes: u64,
}
impl EntitySnapshot {
#[must_use]
pub(crate) const fn new(store: String, path: String, entries: u64, memory_bytes: u64) -> Self {
Self {
store,
path,
entries,
memory_bytes,
}
}
#[must_use]
pub const fn store(&self) -> &str {
self.store.as_str()
}
#[must_use]
pub const fn path(&self) -> &str {
self.path.as_str()
}
#[must_use]
pub const fn entries(&self) -> u64 {
self.entries
}
#[must_use]
pub const fn memory_bytes(&self) -> u64 {
self.memory_bytes
}
}