1use crate::db::index::IndexState;
7use candid::CandidType;
8use serde::Deserialize;
9
10#[cfg_attr(doc, doc = "StorageReport\n\nLive storage snapshot payload.")]
11#[derive(CandidType, Clone, Debug, Default, Deserialize)]
12pub struct StorageReport {
13 pub(crate) storage_data: Vec<DataStoreSnapshot>,
14 pub(crate) storage_index: Vec<IndexStoreSnapshot>,
15 pub(crate) schema_storage: Vec<SchemaStoreSnapshot>,
16 pub(crate) entity_storage: Vec<EntitySnapshot>,
17 pub(crate) corrupted_keys: u64,
18 pub(crate) corrupted_entries: u64,
19}
20
21#[cfg_attr(
22 doc,
23 doc = "IntegrityTotals\n\nAggregated integrity-scan counters across all stores."
24)]
25#[derive(CandidType, Clone, Debug, Default, Deserialize)]
26pub struct IntegrityTotals {
27 pub(crate) data_rows_scanned: u64,
28 pub(crate) index_entries_scanned: u64,
29 pub(crate) corrupted_data_keys: u64,
30 pub(crate) corrupted_data_rows: u64,
31 pub(crate) corrupted_index_keys: u64,
32 pub(crate) corrupted_index_entries: u64,
33 pub(crate) missing_index_entries: u64,
34 pub(crate) divergent_index_entries: u64,
35 pub(crate) orphan_index_references: u64,
36 pub(crate) misuse_findings: u64,
37}
38
39impl IntegrityTotals {
40 pub(super) const fn add_store_snapshot(&mut self, store: &IntegrityStoreSnapshot) {
41 self.data_rows_scanned = self
42 .data_rows_scanned
43 .saturating_add(store.data_rows_scanned);
44 self.index_entries_scanned = self
45 .index_entries_scanned
46 .saturating_add(store.index_entries_scanned);
47 self.corrupted_data_keys = self
48 .corrupted_data_keys
49 .saturating_add(store.corrupted_data_keys);
50 self.corrupted_data_rows = self
51 .corrupted_data_rows
52 .saturating_add(store.corrupted_data_rows);
53 self.corrupted_index_keys = self
54 .corrupted_index_keys
55 .saturating_add(store.corrupted_index_keys);
56 self.corrupted_index_entries = self
57 .corrupted_index_entries
58 .saturating_add(store.corrupted_index_entries);
59 self.missing_index_entries = self
60 .missing_index_entries
61 .saturating_add(store.missing_index_entries);
62 self.divergent_index_entries = self
63 .divergent_index_entries
64 .saturating_add(store.divergent_index_entries);
65 self.orphan_index_references = self
66 .orphan_index_references
67 .saturating_add(store.orphan_index_references);
68 self.misuse_findings = self.misuse_findings.saturating_add(store.misuse_findings);
69 }
70
71 #[must_use]
73 pub const fn data_rows_scanned(&self) -> u64 {
74 self.data_rows_scanned
75 }
76
77 #[must_use]
79 pub const fn index_entries_scanned(&self) -> u64 {
80 self.index_entries_scanned
81 }
82
83 #[must_use]
85 pub const fn corrupted_data_keys(&self) -> u64 {
86 self.corrupted_data_keys
87 }
88
89 #[must_use]
91 pub const fn corrupted_data_rows(&self) -> u64 {
92 self.corrupted_data_rows
93 }
94
95 #[must_use]
97 pub const fn corrupted_index_keys(&self) -> u64 {
98 self.corrupted_index_keys
99 }
100
101 #[must_use]
103 pub const fn corrupted_index_entries(&self) -> u64 {
104 self.corrupted_index_entries
105 }
106
107 #[must_use]
109 pub const fn missing_index_entries(&self) -> u64 {
110 self.missing_index_entries
111 }
112
113 #[must_use]
115 pub const fn divergent_index_entries(&self) -> u64 {
116 self.divergent_index_entries
117 }
118
119 #[must_use]
121 pub const fn orphan_index_references(&self) -> u64 {
122 self.orphan_index_references
123 }
124
125 #[must_use]
127 pub const fn misuse_findings(&self) -> u64 {
128 self.misuse_findings
129 }
130}
131
132#[cfg_attr(
133 doc,
134 doc = "IntegrityStoreSnapshot\n\nPer-store integrity findings and scan counters."
135)]
136#[derive(CandidType, Clone, Debug, Default, Deserialize)]
137pub struct IntegrityStoreSnapshot {
138 pub(crate) path: String,
139 pub(crate) data_rows_scanned: u64,
140 pub(crate) index_entries_scanned: u64,
141 pub(crate) corrupted_data_keys: u64,
142 pub(crate) corrupted_data_rows: u64,
143 pub(crate) corrupted_index_keys: u64,
144 pub(crate) corrupted_index_entries: u64,
145 pub(crate) missing_index_entries: u64,
146 pub(crate) divergent_index_entries: u64,
147 pub(crate) orphan_index_references: u64,
148 pub(crate) misuse_findings: u64,
149}
150
151impl IntegrityStoreSnapshot {
152 #[must_use]
154 pub(crate) fn new(path: String) -> Self {
155 Self {
156 path,
157 ..Self::default()
158 }
159 }
160
161 #[must_use]
163 pub const fn path(&self) -> &str {
164 self.path.as_str()
165 }
166
167 #[must_use]
169 pub const fn data_rows_scanned(&self) -> u64 {
170 self.data_rows_scanned
171 }
172
173 #[must_use]
175 pub const fn index_entries_scanned(&self) -> u64 {
176 self.index_entries_scanned
177 }
178
179 #[must_use]
181 pub const fn corrupted_data_keys(&self) -> u64 {
182 self.corrupted_data_keys
183 }
184
185 #[must_use]
187 pub const fn corrupted_data_rows(&self) -> u64 {
188 self.corrupted_data_rows
189 }
190
191 #[must_use]
193 pub const fn corrupted_index_keys(&self) -> u64 {
194 self.corrupted_index_keys
195 }
196
197 #[must_use]
199 pub const fn corrupted_index_entries(&self) -> u64 {
200 self.corrupted_index_entries
201 }
202
203 #[must_use]
205 pub const fn missing_index_entries(&self) -> u64 {
206 self.missing_index_entries
207 }
208
209 #[must_use]
211 pub const fn divergent_index_entries(&self) -> u64 {
212 self.divergent_index_entries
213 }
214
215 #[must_use]
217 pub const fn orphan_index_references(&self) -> u64 {
218 self.orphan_index_references
219 }
220
221 #[must_use]
223 pub const fn misuse_findings(&self) -> u64 {
224 self.misuse_findings
225 }
226}
227
228#[cfg_attr(
229 doc,
230 doc = "IntegrityReport\n\nFull integrity-scan output across all registered stores."
231)]
232#[derive(CandidType, Clone, Debug, Default, Deserialize)]
233pub struct IntegrityReport {
234 pub(crate) stores: Vec<IntegrityStoreSnapshot>,
235 pub(crate) totals: IntegrityTotals,
236}
237
238impl IntegrityReport {
239 #[must_use]
241 pub(crate) const fn new(stores: Vec<IntegrityStoreSnapshot>, totals: IntegrityTotals) -> Self {
242 Self { stores, totals }
243 }
244
245 #[must_use]
247 pub const fn stores(&self) -> &[IntegrityStoreSnapshot] {
248 self.stores.as_slice()
249 }
250
251 #[must_use]
253 pub const fn totals(&self) -> &IntegrityTotals {
254 &self.totals
255 }
256}
257
258impl StorageReport {
259 #[must_use]
261 pub(crate) const fn new(
262 storage_data: Vec<DataStoreSnapshot>,
263 storage_index: Vec<IndexStoreSnapshot>,
264 schema_storage: Vec<SchemaStoreSnapshot>,
265 entity_storage: Vec<EntitySnapshot>,
266 corrupted_keys: u64,
267 corrupted_entries: u64,
268 ) -> Self {
269 Self {
270 storage_data,
271 storage_index,
272 schema_storage,
273 entity_storage,
274 corrupted_keys,
275 corrupted_entries,
276 }
277 }
278
279 #[must_use]
281 pub const fn storage_data(&self) -> &[DataStoreSnapshot] {
282 self.storage_data.as_slice()
283 }
284
285 #[must_use]
287 pub const fn storage_index(&self) -> &[IndexStoreSnapshot] {
288 self.storage_index.as_slice()
289 }
290
291 #[must_use]
293 pub const fn schema_storage(&self) -> &[SchemaStoreSnapshot] {
294 self.schema_storage.as_slice()
295 }
296
297 #[must_use]
299 pub const fn entity_storage(&self) -> &[EntitySnapshot] {
300 self.entity_storage.as_slice()
301 }
302
303 #[must_use]
305 pub const fn corrupted_keys(&self) -> u64 {
306 self.corrupted_keys
307 }
308
309 #[must_use]
311 pub const fn corrupted_entries(&self) -> u64 {
312 self.corrupted_entries
313 }
314}
315
316#[cfg_attr(doc, doc = "SchemaStoreSnapshot\n\nSchema-store diagnostic row.")]
317#[derive(CandidType, Clone, Debug, Default, Deserialize)]
318pub struct SchemaStoreSnapshot {
319 pub(crate) path: String,
320 pub(crate) storage: StoreSnapshotStorageMode,
321 pub(crate) memory_id: Option<u8>,
322 pub(crate) stable_key: Option<String>,
323 pub(crate) schema_version: Option<u32>,
324 pub(crate) schema_fingerprint: Option<String>,
325 pub(crate) entity_count: u64,
326}
327
328#[derive(CandidType, Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
333pub enum StoreSnapshotStorageMode {
334 #[default]
335 Stable,
336}
337
338impl StoreSnapshotStorageMode {
339 #[must_use]
341 pub const fn as_str(self) -> &'static str {
342 match self {
343 Self::Stable => "stable",
344 }
345 }
346}
347
348#[derive(Clone, Debug, Eq, PartialEq)]
349pub(crate) struct StoreSnapshotAllocationIdentity {
350 memory_id: u8,
351 stable_key: String,
352}
353
354impl StoreSnapshotAllocationIdentity {
355 pub(crate) const fn new(memory_id: u8, stable_key: String) -> Self {
356 Self {
357 memory_id,
358 stable_key,
359 }
360 }
361
362 const fn memory_id(&self) -> u8 {
363 self.memory_id
364 }
365}
366
367#[derive(Clone, Debug, Default, Eq, PartialEq)]
368pub(crate) struct StoreSnapshotSchemaMetadata {
369 schema_version: Option<u32>,
370 schema_fingerprint: Option<String>,
371}
372
373impl StoreSnapshotSchemaMetadata {
374 pub(crate) const fn absent() -> Self {
375 Self {
376 schema_version: None,
377 schema_fingerprint: None,
378 }
379 }
380
381 pub(crate) const fn new(schema_version: u32, schema_fingerprint: String) -> Self {
382 Self {
383 schema_version: Some(schema_version),
384 schema_fingerprint: Some(schema_fingerprint),
385 }
386 }
387
388 const fn schema_version(&self) -> Option<u32> {
389 self.schema_version
390 }
391
392 fn schema_fingerprint(&self) -> Option<String> {
393 self.schema_fingerprint.clone()
394 }
395}
396
397#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
398pub(crate) struct IndexStoreSnapshotStats {
399 entries: u64,
400 user_entries: u64,
401 system_entries: u64,
402 memory_bytes: u64,
403 state: IndexState,
404}
405
406impl IndexStoreSnapshotStats {
407 pub(crate) const fn new(
408 entries: u64,
409 user_entries: u64,
410 system_entries: u64,
411 memory_bytes: u64,
412 state: IndexState,
413 ) -> Self {
414 Self {
415 entries,
416 user_entries,
417 system_entries,
418 memory_bytes,
419 state,
420 }
421 }
422}
423
424impl SchemaStoreSnapshot {
425 #[must_use]
427 pub(crate) fn new(
428 path: String,
429 storage: StoreSnapshotStorageMode,
430 allocation: Option<StoreSnapshotAllocationIdentity>,
431 schema_metadata: StoreSnapshotSchemaMetadata,
432 entity_count: u64,
433 ) -> Self {
434 let memory_id = allocation
435 .as_ref()
436 .map(StoreSnapshotAllocationIdentity::memory_id);
437 let stable_key = match allocation {
438 Some(allocation) => Some(allocation.stable_key),
439 None => None,
440 };
441 Self {
442 path,
443 storage,
444 memory_id,
445 stable_key,
446 schema_version: schema_metadata.schema_version(),
447 schema_fingerprint: schema_metadata.schema_fingerprint(),
448 entity_count,
449 }
450 }
451
452 #[must_use]
454 pub const fn path(&self) -> &str {
455 self.path.as_str()
456 }
457
458 #[must_use]
460 pub const fn storage(&self) -> StoreSnapshotStorageMode {
461 self.storage
462 }
463
464 #[must_use]
466 pub const fn memory_id(&self) -> Option<u8> {
467 self.memory_id
468 }
469
470 #[must_use]
472 pub const fn stable_key(&self) -> Option<&str> {
473 match &self.stable_key {
474 Some(value) => Some(value.as_str()),
475 None => None,
476 }
477 }
478
479 #[must_use]
481 pub const fn schema_version(&self) -> Option<u32> {
482 self.schema_version
483 }
484
485 #[must_use]
487 pub const fn schema_fingerprint(&self) -> Option<&str> {
488 match &self.schema_fingerprint {
489 Some(value) => Some(value.as_str()),
490 None => None,
491 }
492 }
493
494 #[must_use]
496 pub const fn entity_count(&self) -> u64 {
497 self.entity_count
498 }
499}
500
501#[cfg_attr(doc, doc = "DataStoreSnapshot\n\nData-store snapshot row.")]
502#[derive(CandidType, Clone, Debug, Default, Deserialize)]
503pub struct DataStoreSnapshot {
504 pub(crate) path: String,
505 pub(crate) storage: StoreSnapshotStorageMode,
506 pub(crate) memory_id: Option<u8>,
507 pub(crate) stable_key: Option<String>,
508 pub(crate) schema_version: Option<u32>,
509 pub(crate) schema_fingerprint: Option<String>,
510 pub(crate) entries: u64,
511 pub(crate) memory_bytes: u64,
512}
513
514impl DataStoreSnapshot {
515 #[must_use]
517 pub(crate) fn new(
518 path: String,
519 storage: StoreSnapshotStorageMode,
520 allocation: Option<StoreSnapshotAllocationIdentity>,
521 schema_metadata: StoreSnapshotSchemaMetadata,
522 entries: u64,
523 memory_bytes: u64,
524 ) -> Self {
525 let memory_id = allocation
526 .as_ref()
527 .map(StoreSnapshotAllocationIdentity::memory_id);
528 let stable_key = match allocation {
529 Some(allocation) => Some(allocation.stable_key),
530 None => None,
531 };
532 Self {
533 path,
534 storage,
535 memory_id,
536 stable_key,
537 schema_version: schema_metadata.schema_version(),
538 schema_fingerprint: schema_metadata.schema_fingerprint(),
539 entries,
540 memory_bytes,
541 }
542 }
543
544 #[must_use]
546 pub const fn path(&self) -> &str {
547 self.path.as_str()
548 }
549
550 #[must_use]
552 pub const fn storage(&self) -> StoreSnapshotStorageMode {
553 self.storage
554 }
555
556 #[must_use]
558 pub const fn memory_id(&self) -> Option<u8> {
559 self.memory_id
560 }
561
562 #[must_use]
564 pub const fn stable_key(&self) -> Option<&str> {
565 match &self.stable_key {
566 Some(value) => Some(value.as_str()),
567 None => None,
568 }
569 }
570
571 #[must_use]
573 pub const fn schema_version(&self) -> Option<u32> {
574 self.schema_version
575 }
576
577 #[must_use]
579 pub const fn schema_fingerprint(&self) -> Option<&str> {
580 match &self.schema_fingerprint {
581 Some(value) => Some(value.as_str()),
582 None => None,
583 }
584 }
585
586 #[must_use]
588 pub const fn entries(&self) -> u64 {
589 self.entries
590 }
591
592 #[must_use]
594 pub const fn memory_bytes(&self) -> u64 {
595 self.memory_bytes
596 }
597}
598
599#[cfg_attr(doc, doc = "IndexStoreSnapshot\n\nIndex-store snapshot row.")]
600#[derive(CandidType, Clone, Debug, Default, Deserialize)]
601pub struct IndexStoreSnapshot {
602 pub(crate) path: String,
603 pub(crate) storage: StoreSnapshotStorageMode,
604 pub(crate) memory_id: Option<u8>,
605 pub(crate) stable_key: Option<String>,
606 pub(crate) schema_version: Option<u32>,
607 pub(crate) schema_fingerprint: Option<String>,
608 pub(crate) entries: u64,
609 pub(crate) user_entries: u64,
610 pub(crate) system_entries: u64,
611 pub(crate) memory_bytes: u64,
612 pub(crate) state: IndexState,
613}
614
615impl IndexStoreSnapshot {
616 #[must_use]
618 pub(crate) fn new(
619 path: String,
620 storage: StoreSnapshotStorageMode,
621 allocation: Option<StoreSnapshotAllocationIdentity>,
622 schema_metadata: StoreSnapshotSchemaMetadata,
623 stats: IndexStoreSnapshotStats,
624 ) -> Self {
625 let memory_id = allocation
626 .as_ref()
627 .map(StoreSnapshotAllocationIdentity::memory_id);
628 let stable_key = match allocation {
629 Some(allocation) => Some(allocation.stable_key),
630 None => None,
631 };
632 Self {
633 path,
634 storage,
635 memory_id,
636 stable_key,
637 schema_version: schema_metadata.schema_version(),
638 schema_fingerprint: schema_metadata.schema_fingerprint(),
639 entries: stats.entries,
640 user_entries: stats.user_entries,
641 system_entries: stats.system_entries,
642 memory_bytes: stats.memory_bytes,
643 state: stats.state,
644 }
645 }
646
647 #[must_use]
649 pub const fn path(&self) -> &str {
650 self.path.as_str()
651 }
652
653 #[must_use]
655 pub const fn storage(&self) -> StoreSnapshotStorageMode {
656 self.storage
657 }
658
659 #[must_use]
661 pub const fn memory_id(&self) -> Option<u8> {
662 self.memory_id
663 }
664
665 #[must_use]
667 pub const fn stable_key(&self) -> Option<&str> {
668 match &self.stable_key {
669 Some(value) => Some(value.as_str()),
670 None => None,
671 }
672 }
673
674 #[must_use]
676 pub const fn schema_version(&self) -> Option<u32> {
677 self.schema_version
678 }
679
680 #[must_use]
682 pub const fn schema_fingerprint(&self) -> Option<&str> {
683 match &self.schema_fingerprint {
684 Some(value) => Some(value.as_str()),
685 None => None,
686 }
687 }
688
689 #[must_use]
691 pub const fn entries(&self) -> u64 {
692 self.entries
693 }
694
695 #[must_use]
697 pub const fn user_entries(&self) -> u64 {
698 self.user_entries
699 }
700
701 #[must_use]
703 pub const fn system_entries(&self) -> u64 {
704 self.system_entries
705 }
706
707 #[must_use]
709 pub const fn memory_bytes(&self) -> u64 {
710 self.memory_bytes
711 }
712
713 #[must_use]
716 pub const fn state(&self) -> IndexState {
717 self.state
718 }
719}
720
721#[cfg_attr(doc, doc = "EntitySnapshot\n\nPer-entity storage snapshot row.")]
722#[derive(CandidType, Clone, Debug, Default, Deserialize)]
723pub struct EntitySnapshot {
724 pub(crate) store: String,
725
726 pub(crate) path: String,
727
728 pub(crate) entries: u64,
729
730 pub(crate) memory_bytes: u64,
731}
732
733impl EntitySnapshot {
734 #[must_use]
736 pub(crate) const fn new(store: String, path: String, entries: u64, memory_bytes: u64) -> Self {
737 Self {
738 store,
739 path,
740 entries,
741 memory_bytes,
742 }
743 }
744
745 #[must_use]
747 pub const fn store(&self) -> &str {
748 self.store.as_str()
749 }
750
751 #[must_use]
753 pub const fn path(&self) -> &str {
754 self.path.as_str()
755 }
756
757 #[must_use]
759 pub const fn entries(&self) -> u64 {
760 self.entries
761 }
762
763 #[must_use]
765 pub const fn memory_bytes(&self) -> u64 {
766 self.memory_bytes
767 }
768}