Skip to main content

orbok_db/repo/
storage.rs

1//! Storage accounting repository (RFC-002 §7.12, RFC-001 §10).
2
3use crate::catalog::{Catalog, db_err};
4use orbok_core::{OrbokResult, StorageCategory, now_iso8601};
5use rusqlite::params;
6
7/// One accounting row per [`StorageCategory`].
8#[derive(Debug, Clone)]
9pub struct StorageRow {
10    pub category: StorageCategory,
11    pub size_bytes: u64,
12    pub item_count: u64,
13    pub updated_at: String,
14}
15
16pub struct StorageAccountingRepository<'a> {
17    catalog: &'a Catalog,
18}
19
20impl<'a> StorageAccountingRepository<'a> {
21    pub fn new(catalog: &'a Catalog) -> Self {
22        Self { catalog }
23    }
24
25    /// Record (or refresh) the measured size of one category.
26    pub fn upsert(
27        &self,
28        category: StorageCategory,
29        size_bytes: u64,
30        item_count: u64,
31    ) -> OrbokResult<()> {
32        let conn = self.catalog.lock();
33        conn.execute(
34            "INSERT INTO storage_accounting (category, size_bytes, item_count, updated_at) \
35             VALUES (?1, ?2, ?3, ?4) \
36             ON CONFLICT(category) DO UPDATE SET size_bytes = ?2, item_count = ?3, updated_at = ?4",
37            params![
38                category.as_str(),
39                size_bytes as i64,
40                item_count as i64,
41                now_iso8601()
42            ],
43        )
44        .map_err(db_err)?;
45        Ok(())
46    }
47
48    /// All recorded categories (Storage view breakdown).
49    pub fn all(&self) -> OrbokResult<Vec<StorageRow>> {
50        let conn = self.catalog.lock();
51        let mut stmt = conn
52            .prepare("SELECT category, size_bytes, item_count, updated_at FROM storage_accounting")
53            .map_err(db_err)?;
54        let rows = stmt
55            .query_map([], |row| {
56                Ok((
57                    row.get::<_, String>(0)?,
58                    row.get::<_, i64>(1)?,
59                    row.get::<_, i64>(2)?,
60                    row.get::<_, String>(3)?,
61                ))
62            })
63            .map_err(db_err)?;
64        let mut out = Vec::new();
65        for row in rows {
66            let (cat, size, count, updated) = row.map_err(db_err)?;
67            // Unknown categories from future versions are skipped, not fatal.
68            if let Some(category) = StorageCategory::ALL
69                .iter()
70                .find(|c| c.as_str() == cat)
71                .copied()
72            {
73                out.push(StorageRow {
74                    category,
75                    size_bytes: size as u64,
76                    item_count: count as u64,
77                    updated_at: updated,
78                });
79            }
80        }
81        Ok(out)
82    }
83}