//! Repository structure and storage measurements used by Doctor. module storage /// Storage counts and structural findings from one database snapshot. pub struct DoctorStats { pub mut: objects i64 saves i64 checkpoints i64 cached_files i64 file_entries i64 object_bytes i64 packed_bytes i64 unused i64 pending []string state map[string]string problems []string } /// Inspect references, scalar storage invariants and machine-local state. pub fn (s &Store) doctor_stats() !DoctorStats { mut out := DoctorStats{} mut sizes := s.db.prepare('SELECT COUNT(*), COALESCE(SUM(size), 0), COALESCE(SUM(length(data)), 0) FROM objects')! defer { sizes.finalize() } if sizes.step()! { out.objects = sizes.int(0) out.object_bytes = sizes.int(1) out.packed_bytes = sizes.int(2) } mut counts := s.db.prepare('SELECT (SELECT COUNT(*) FROM saves), (SELECT COUNT(*) FROM saves WHERE is_checkpoint = 1), (SELECT COUNT(*) FROM file_cache), (SELECT COUNT(*) FROM save_files), (SELECT COUNT(*) FROM objects WHERE hash NOT IN (SELECT hash FROM save_files))')! defer { counts.finalize() } if counts.step()! { out.saves = counts.int(0) out.checkpoints = counts.int(1) out.cached_files = counts.int(2) out.file_entries = counts.int(3) out.unused = counts.int(4) } mut foreign := s.db.prepare('PRAGMA foreign_key_check')! defer { foreign.finalize() } for foreign.step()! { out.problems << 'broken reference in ${foreign.text(0)} row ${foreign.int(1)} to ${foreign.text(2)}' } mut invalid := s.db.prepare("SELECT save_id, path FROM save_files WHERE executable NOT IN (0, 1) OR typeof(executable) != 'integer' UNION ALL SELECT id, 'invalid save metadata' FROM saves WHERE is_checkpoint NOT IN (0, 1) OR typeof(is_checkpoint) != 'integer' OR id = '' OR typeof(created_at) != 'integer' OR created_at < 0 OR typeof(size_bytes) != 'integer' OR size_bytes < 0")! defer { invalid.finalize() } for invalid.step()! { out.problems << 'invalid metadata in save ${invalid.text(0)}: ${invalid.text(1)}' } mut state := s.db.prepare('SELECT key, value FROM state')! defer { state.finalize() } for state.step()! { key := state.text(0) out.state[key] = state.text(1) if key.starts_with('strip_pending:') { out.pending << key.all_after('strip_pending:') } } out.pending.sort() return out }