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