Skip to main content

sqlite_graphrag/commands/
vacuum.rs

1use crate::errors::AppError;
2use crate::i18n::erros;
3use crate::output;
4use crate::output::JsonOutputFormat;
5use crate::paths::AppPaths;
6use crate::storage::connection::open_rw;
7use serde::Serialize;
8
9#[derive(clap::Args)]
10pub struct VacuumArgs {
11    #[arg(long, hide = true, help = "No-op; JSON is always emitted on stdout")]
12    pub json: bool,
13    /// Executar WAL checkpoint antes e depois do VACUUM (padrão: true).
14    #[arg(long, default_value_t = true)]
15    pub checkpoint: bool,
16    /// Formato da saída.
17    #[arg(long, value_enum, default_value_t = JsonOutputFormat::Json)]
18    pub format: JsonOutputFormat,
19    #[arg(long, env = "SQLITE_GRAPHRAG_DB_PATH")]
20    pub db: Option<String>,
21}
22
23#[derive(Serialize)]
24struct VacuumResponse {
25    db_path: String,
26    size_before_bytes: u64,
27    size_after_bytes: u64,
28    status: String,
29    /// Tempo total de execução em milissegundos desde início do handler até serialização.
30    elapsed_ms: u64,
31}
32
33pub fn run(args: VacuumArgs) -> Result<(), AppError> {
34    let inicio = std::time::Instant::now();
35    let _ = args.format;
36    let paths = AppPaths::resolve(args.db.as_deref())?;
37
38    if !paths.db.exists() {
39        return Err(AppError::NotFound(erros::banco_nao_encontrado(
40            &paths.db.display().to_string(),
41        )));
42    }
43
44    let size_before_bytes = std::fs::metadata(&paths.db)
45        .map(|meta| meta.len())
46        .unwrap_or(0);
47    let conn = open_rw(&paths.db)?;
48    if args.checkpoint {
49        conn.execute_batch("PRAGMA wal_checkpoint(TRUNCATE);")?;
50    }
51    conn.execute_batch("VACUUM;")?;
52    if args.checkpoint {
53        conn.execute_batch("PRAGMA wal_checkpoint(TRUNCATE);")?;
54    }
55    drop(conn);
56    let size_after_bytes = std::fs::metadata(&paths.db)
57        .map(|meta| meta.len())
58        .unwrap_or(0);
59
60    output::emit_json(&VacuumResponse {
61        db_path: paths.db.display().to_string(),
62        size_before_bytes,
63        size_after_bytes,
64        status: "ok".to_string(),
65        elapsed_ms: inicio.elapsed().as_millis() as u64,
66    })?;
67
68    Ok(())
69}
70
71#[cfg(test)]
72mod testes {
73    use super::*;
74
75    #[test]
76    fn vacuum_response_serializa_todos_campos() {
77        let resp = VacuumResponse {
78            db_path: "/home/user/.local/share/sqlite-graphrag/db.sqlite".to_string(),
79            size_before_bytes: 32768,
80            size_after_bytes: 16384,
81            status: "ok".to_string(),
82            elapsed_ms: 55,
83        };
84        let json = serde_json::to_value(&resp).expect("serialização falhou");
85        assert_eq!(
86            json["db_path"],
87            "/home/user/.local/share/sqlite-graphrag/db.sqlite"
88        );
89        assert_eq!(json["size_before_bytes"], 32768u64);
90        assert_eq!(json["size_after_bytes"], 16384u64);
91        assert_eq!(json["status"], "ok");
92        assert_eq!(json["elapsed_ms"], 55u64);
93    }
94
95    #[test]
96    fn vacuum_response_size_after_menor_ou_igual_before() {
97        let resp = VacuumResponse {
98            db_path: "/data/db.sqlite".to_string(),
99            size_before_bytes: 65536,
100            size_after_bytes: 32768,
101            status: "ok".to_string(),
102            elapsed_ms: 100,
103        };
104        let json = serde_json::to_value(&resp).expect("serialização falhou");
105        let before = json["size_before_bytes"].as_u64().unwrap();
106        let after = json["size_after_bytes"].as_u64().unwrap();
107        assert!(
108            after <= before,
109            "size_after_bytes deve ser <= size_before_bytes após VACUUM"
110        );
111    }
112
113    #[test]
114    fn vacuum_response_status_ok() {
115        let resp = VacuumResponse {
116            db_path: "/data/db.sqlite".to_string(),
117            size_before_bytes: 0,
118            size_after_bytes: 0,
119            status: "ok".to_string(),
120            elapsed_ms: 0,
121        };
122        let json = serde_json::to_value(&resp).expect("serialização falhou");
123        assert_eq!(json["status"], "ok");
124    }
125
126    #[test]
127    fn vacuum_response_elapsed_ms_presente_e_nao_negativo() {
128        let resp = VacuumResponse {
129            db_path: "/data/db.sqlite".to_string(),
130            size_before_bytes: 1024,
131            size_after_bytes: 1024,
132            status: "ok".to_string(),
133            elapsed_ms: 0,
134        };
135        let json = serde_json::to_value(&resp).expect("serialização falhou");
136        assert!(
137            json.get("elapsed_ms").is_some(),
138            "campo elapsed_ms deve estar presente"
139        );
140        assert!(
141            json["elapsed_ms"].as_u64().is_some(),
142            "elapsed_ms deve ser inteiro não negativo"
143        );
144    }
145}