sqlite_graphrag/commands/
forget.rs1use crate::errors::AppError;
2use crate::i18n::erros;
3use crate::output;
4use crate::paths::AppPaths;
5use crate::storage::connection::open_rw;
6use crate::storage::memories;
7use serde::Serialize;
8
9#[derive(clap::Args)]
10pub struct ForgetArgs {
11 #[arg(long)]
12 pub name: String,
13 #[arg(long, default_value = "global")]
14 pub namespace: Option<String>,
15 #[arg(long, hide = true, help = "No-op; JSON is always emitted on stdout")]
16 pub json: bool,
17 #[arg(long, env = "SQLITE_GRAPHRAG_DB_PATH")]
18 pub db: Option<String>,
19}
20
21#[derive(Serialize)]
22struct ForgetResponse {
23 forgotten: bool,
24 name: String,
25 namespace: String,
26 elapsed_ms: u64,
28}
29
30pub fn run(args: ForgetArgs) -> Result<(), AppError> {
31 let inicio = std::time::Instant::now();
32 let namespace = crate::namespace::resolve_namespace(args.namespace.as_deref())?;
33 let paths = AppPaths::resolve(args.db.as_deref())?;
34
35 let conn = open_rw(&paths.db)?;
36
37 let maybe_row = memories::read_by_name(&conn, &namespace, &args.name)?;
38 let forgotten = memories::soft_delete(&conn, &namespace, &args.name)?;
39
40 if !forgotten {
41 return Err(AppError::NotFound(erros::memoria_nao_encontrada(
42 &args.name, &namespace,
43 )));
44 }
45
46 if let Some(row) = maybe_row {
47 if let Err(e) = memories::delete_vec(&conn, row.id) {
52 tracing::warn!(memory_id = row.id, error = %e, "vec cleanup failed — orphan vector left");
53 }
54 }
55
56 output::emit_json(&ForgetResponse {
57 forgotten: true,
58 name: args.name,
59 namespace,
60 elapsed_ms: inicio.elapsed().as_millis() as u64,
61 })?;
62
63 Ok(())
64}
65
66#[cfg(test)]
67mod testes {
68 use super::*;
69
70 #[test]
71 fn forget_response_serializa_campos_basicos() {
72 let resp = ForgetResponse {
73 forgotten: true,
74 name: "minha-memoria".to_string(),
75 namespace: "global".to_string(),
76 elapsed_ms: 5,
77 };
78 let json = serde_json::to_value(&resp).expect("serialização falhou");
79 assert_eq!(json["forgotten"], true);
80 assert_eq!(json["name"], "minha-memoria");
81 assert_eq!(json["namespace"], "global");
82 assert!(json["elapsed_ms"].is_number());
83 }
84
85 #[test]
86 fn forget_response_forgotten_true_indica_sucesso() {
87 let resp = ForgetResponse {
88 forgotten: true,
89 name: "teste".to_string(),
90 namespace: "ns".to_string(),
91 elapsed_ms: 1,
92 };
93 assert!(
94 resp.forgotten,
95 "forgotten deve ser true quando soft-delete bem-sucedido"
96 );
97 }
98
99 #[test]
100 fn forget_resposta_com_namespace_correto() {
101 let resp = ForgetResponse {
102 forgotten: true,
103 name: "abc".to_string(),
104 namespace: "meu-projeto".to_string(),
105 elapsed_ms: 0,
106 };
107 assert_eq!(
108 resp.namespace, "meu-projeto",
109 "namespace deve ser preservado na resposta"
110 );
111 }
112
113 #[test]
114 fn forget_elapsed_ms_zero_e_valido() {
115 let resp = ForgetResponse {
116 forgotten: true,
117 name: "qualquer".to_string(),
118 namespace: "global".to_string(),
119 elapsed_ms: 0,
120 };
121 let json = serde_json::to_value(&resp).expect("serialização falhou");
122 assert_eq!(json["elapsed_ms"], 0u64);
123 }
124}