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![category.as_str(), size_bytes as i64, item_count as i64, now_iso8601()],
38        )
39        .map_err(db_err)?;
40        Ok(())
41    }
42
43    /// All recorded categories (Storage view breakdown).
44    pub fn all(&self) -> OrbokResult<Vec<StorageRow>> {
45        let conn = self.catalog.lock();
46        let mut stmt = conn
47            .prepare("SELECT category, size_bytes, item_count, updated_at FROM storage_accounting")
48            .map_err(db_err)?;
49        let rows = stmt
50            .query_map([], |row| {
51                Ok((
52                    row.get::<_, String>(0)?,
53                    row.get::<_, i64>(1)?,
54                    row.get::<_, i64>(2)?,
55                    row.get::<_, String>(3)?,
56                ))
57            })
58            .map_err(db_err)?;
59        let mut out = Vec::new();
60        for row in rows {
61            let (cat, size, count, updated) = row.map_err(db_err)?;
62            // Unknown categories from future versions are skipped, not fatal.
63            if let Some(category) = StorageCategory::ALL
64                .iter()
65                .find(|c| c.as_str() == cat)
66                .copied()
67            {
68                out.push(StorageRow {
69                    category,
70                    size_bytes: size as u64,
71                    item_count: count as u64,
72                    updated_at: updated,
73                });
74            }
75        }
76        Ok(out)
77    }
78}