sqlite_graphrag/commands/
stats.rs1use crate::errors::AppError;
2use crate::i18n::erros;
3use crate::output;
4use crate::paths::AppPaths;
5use crate::storage::connection::open_ro;
6use serde::Serialize;
7
8#[derive(clap::Args)]
9pub struct StatsArgs {
10 #[arg(long, env = "SQLITE_GRAPHRAG_DB_PATH")]
11 pub db: Option<String>,
12 #[arg(long, default_value_t = false)]
14 pub json: bool,
15 #[arg(long, value_parser = ["json", "text"], hide = true)]
17 pub format: Option<String>,
18}
19
20#[derive(Serialize)]
21struct StatsResponse {
22 memories: i64,
23 memories_total: i64,
25 entities: i64,
26 entities_total: i64,
28 relationships: i64,
29 relationships_total: i64,
31 edges: i64,
33 chunks_total: i64,
35 avg_body_len: f64,
37 namespaces: Vec<String>,
38 db_size_bytes: u64,
39 db_bytes: u64,
41 schema_version: String,
42 elapsed_ms: u64,
44}
45
46pub fn run(args: StatsArgs) -> Result<(), AppError> {
47 let inicio = std::time::Instant::now();
48 let _ = args.json; let _ = args.format; let paths = AppPaths::resolve(args.db.as_deref())?;
51
52 if !paths.db.exists() {
53 return Err(AppError::NotFound(erros::banco_nao_encontrado(
54 &paths.db.display().to_string(),
55 )));
56 }
57
58 let conn = open_ro(&paths.db)?;
59
60 let memories: i64 = conn.query_row(
61 "SELECT COUNT(*) FROM memories WHERE deleted_at IS NULL",
62 [],
63 |r| r.get(0),
64 )?;
65 let entities: i64 = conn.query_row("SELECT COUNT(*) FROM entities", [], |r| r.get(0))?;
66 let relationships: i64 =
67 conn.query_row("SELECT COUNT(*) FROM relationships", [], |r| r.get(0))?;
68
69 let mut stmt = conn.prepare(
70 "SELECT DISTINCT namespace FROM memories WHERE deleted_at IS NULL ORDER BY namespace",
71 )?;
72 let namespaces: Vec<String> = stmt
73 .query_map([], |r| r.get(0))?
74 .collect::<Result<Vec<_>, _>>()?;
75
76 let schema_version: String = conn
77 .query_row(
78 "SELECT value FROM schema_meta WHERE key='schema_version'",
79 [],
80 |r| r.get(0),
81 )
82 .unwrap_or_else(|_| "unknown".to_string());
83
84 let db_size_bytes = std::fs::metadata(&paths.db).map(|m| m.len()).unwrap_or(0);
85
86 let chunks_total: i64 = conn
87 .query_row("SELECT COUNT(*) FROM memory_chunks", [], |r| r.get(0))
88 .unwrap_or(0);
89
90 let avg_body_len: f64 = conn
91 .query_row(
92 "SELECT COALESCE(AVG(LENGTH(body)), 0.0) FROM memories WHERE deleted_at IS NULL",
93 [],
94 |r| r.get(0),
95 )
96 .unwrap_or(0.0);
97
98 output::emit_json(&StatsResponse {
99 memories,
100 memories_total: memories,
101 entities,
102 entities_total: entities,
103 relationships,
104 relationships_total: relationships,
105 edges: relationships,
106 chunks_total,
107 avg_body_len,
108 namespaces,
109 db_size_bytes,
110 db_bytes: db_size_bytes,
111 schema_version,
112 elapsed_ms: inicio.elapsed().as_millis() as u64,
113 })?;
114
115 Ok(())
116}
117
118#[cfg(test)]
119mod testes {
120 use super::*;
121
122 #[test]
123 fn stats_response_serializa_todos_campos() {
124 let resp = StatsResponse {
125 memories: 10,
126 memories_total: 10,
127 entities: 5,
128 entities_total: 5,
129 relationships: 3,
130 relationships_total: 3,
131 edges: 3,
132 chunks_total: 20,
133 avg_body_len: 42.5,
134 namespaces: vec!["global".to_string(), "projeto".to_string()],
135 db_size_bytes: 8192,
136 db_bytes: 8192,
137 schema_version: "5".to_string(),
138 elapsed_ms: 7,
139 };
140 let json = serde_json::to_value(&resp).expect("serialização falhou");
141 assert_eq!(json["memories"], 10);
142 assert_eq!(json["memories_total"], 10);
143 assert_eq!(json["entities"], 5);
144 assert_eq!(json["entities_total"], 5);
145 assert_eq!(json["relationships"], 3);
146 assert_eq!(json["relationships_total"], 3);
147 assert_eq!(json["edges"], 3);
148 assert_eq!(json["chunks_total"], 20);
149 assert_eq!(json["db_size_bytes"], 8192u64);
150 assert_eq!(json["db_bytes"], 8192u64);
151 assert_eq!(json["schema_version"], "5");
152 assert_eq!(json["elapsed_ms"], 7u64);
153 }
154
155 #[test]
156 fn stats_response_namespaces_eh_array_de_strings() {
157 let resp = StatsResponse {
158 memories: 0,
159 memories_total: 0,
160 entities: 0,
161 entities_total: 0,
162 relationships: 0,
163 relationships_total: 0,
164 edges: 0,
165 chunks_total: 0,
166 avg_body_len: 0.0,
167 namespaces: vec!["ns1".to_string(), "ns2".to_string(), "ns3".to_string()],
168 db_size_bytes: 0,
169 db_bytes: 0,
170 schema_version: "unknown".to_string(),
171 elapsed_ms: 0,
172 };
173 let json = serde_json::to_value(&resp).expect("serialização falhou");
174 let arr = json["namespaces"]
175 .as_array()
176 .expect("namespaces deve ser array");
177 assert_eq!(arr.len(), 3);
178 assert_eq!(arr[0], "ns1");
179 assert_eq!(arr[1], "ns2");
180 assert_eq!(arr[2], "ns3");
181 }
182
183 #[test]
184 fn stats_response_namespaces_vazio_serializa_array_vazio() {
185 let resp = StatsResponse {
186 memories: 0,
187 memories_total: 0,
188 entities: 0,
189 entities_total: 0,
190 relationships: 0,
191 relationships_total: 0,
192 edges: 0,
193 chunks_total: 0,
194 avg_body_len: 0.0,
195 namespaces: vec![],
196 db_size_bytes: 0,
197 db_bytes: 0,
198 schema_version: "unknown".to_string(),
199 elapsed_ms: 0,
200 };
201 let json = serde_json::to_value(&resp).expect("serialização falhou");
202 let arr = json["namespaces"]
203 .as_array()
204 .expect("namespaces deve ser array");
205 assert!(arr.is_empty(), "namespaces vazio deve serializar como []");
206 }
207
208 #[test]
209 fn stats_response_aliases_memories_total_e_memories_iguais() {
210 let resp = StatsResponse {
211 memories: 42,
212 memories_total: 42,
213 entities: 7,
214 entities_total: 7,
215 relationships: 2,
216 relationships_total: 2,
217 edges: 2,
218 chunks_total: 0,
219 avg_body_len: 0.0,
220 namespaces: vec![],
221 db_size_bytes: 0,
222 db_bytes: 0,
223 schema_version: "5".to_string(),
224 elapsed_ms: 0,
225 };
226 let json = serde_json::to_value(&resp).expect("serialização falhou");
227 assert_eq!(json["memories"], json["memories_total"]);
228 assert_eq!(json["entities"], json["entities_total"]);
229 assert_eq!(json["relationships"], json["relationships_total"]);
230 assert_eq!(json["relationships"], json["edges"]);
231 assert_eq!(json["db_size_bytes"], json["db_bytes"]);
232 }
233}