sqlite_graphrag/commands/
debug_schema.rs1use crate::errors::AppError;
2use crate::i18n::erros;
3use crate::output;
4use crate::paths::AppPaths;
5use crate::storage::connection::open_ro;
6use serde::Serialize;
7use std::time::Instant;
8
9#[derive(clap::Args)]
10pub struct DebugSchemaArgs {
11 #[arg(long, hide = true, help = "No-op; JSON is always emitted on stdout")]
12 pub json: bool,
13 #[arg(long, env = "SQLITE_GRAPHRAG_DB_PATH")]
14 pub db: Option<String>,
15}
16
17#[derive(Serialize)]
18struct SchemaObject {
19 name: String,
20 #[serde(rename = "type")]
21 object_type: String,
22}
23
24#[derive(Serialize)]
25struct MigrationRecord {
26 version: i64,
27 name: String,
28 applied_on: String,
29}
30
31#[derive(Serialize)]
32struct DebugSchemaResponse {
33 schema_version: i64,
36 user_version: i64,
40 objects: Vec<SchemaObject>,
41 migrations: Vec<MigrationRecord>,
42 elapsed_ms: u64,
43}
44
45pub fn run(args: DebugSchemaArgs) -> Result<(), AppError> {
46 let inicio = Instant::now();
47 let paths = AppPaths::resolve(args.db.as_deref())?;
48
49 if !paths.db.exists() {
50 return Err(AppError::NotFound(erros::banco_nao_encontrado(
51 &paths.db.display().to_string(),
52 )));
53 }
54
55 let conn = open_ro(&paths.db)?;
56
57 let schema_version: i64 = conn
58 .query_row("PRAGMA schema_version", [], |r| r.get(0))
59 .unwrap_or(0);
60
61 let user_version: i64 = conn
63 .query_row("PRAGMA user_version", [], |r| r.get(0))
64 .unwrap_or(0);
65
66 let mut stmt = conn.prepare(
67 "SELECT name, type FROM sqlite_master \
68 WHERE type IN ('table','view','trigger','index') \
69 ORDER BY type, name",
70 )?;
71 let objects: Vec<SchemaObject> = stmt
72 .query_map([], |r| {
73 Ok(SchemaObject {
74 name: r.get(0)?,
75 object_type: r.get(1)?,
76 })
77 })?
78 .collect::<Result<Vec<_>, _>>()?;
79
80 let existe_hist: i64 = conn
81 .query_row(
82 "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='refinery_schema_history'",
83 [],
84 |r| r.get(0),
85 )
86 .unwrap_or(0);
87
88 let migrations: Vec<MigrationRecord> = if existe_hist > 0 {
89 let mut stmt_mig = conn.prepare(
90 "SELECT version, name, applied_on \
91 FROM refinery_schema_history \
92 ORDER BY version",
93 )?;
94 let rows: Vec<MigrationRecord> = stmt_mig
95 .query_map([], |r| {
96 Ok(MigrationRecord {
97 version: r.get(0)?,
98 name: r.get(1)?,
99 applied_on: r.get(2)?,
100 })
101 })?
102 .collect::<Result<Vec<_>, _>>()?;
103 rows
104 } else {
105 Vec::new()
106 };
107
108 let elapsed_ms = inicio.elapsed().as_millis() as u64;
109
110 output::emit_json(&DebugSchemaResponse {
111 schema_version,
112 user_version,
113 objects,
114 migrations,
115 elapsed_ms,
116 })?;
117
118 Ok(())
119}
120
121#[cfg(test)]
122mod testes {
123 use super::*;
124 use serde_json::Value;
125
126 #[test]
127 fn debug_schema_response_serializa_campos_obrigatorios() {
128 let resp = DebugSchemaResponse {
129 schema_version: 42,
130 user_version: 49,
131 objects: vec![SchemaObject {
132 name: "memories".to_string(),
133 object_type: "table".to_string(),
134 }],
135 migrations: vec![MigrationRecord {
136 version: 1,
137 name: "V001__init".to_string(),
138 applied_on: "2026-01-01T00:00:00Z".to_string(),
139 }],
140 elapsed_ms: 7,
141 };
142 let json: Value = serde_json::to_value(&resp).unwrap();
143 assert_eq!(json["schema_version"], 42);
144 assert_eq!(json["user_version"], 49);
145 assert!(json["objects"].is_array());
146 assert_eq!(json["objects"][0]["name"], "memories");
147 assert_eq!(json["objects"][0]["type"], "table");
148 assert!(json["migrations"].is_array());
149 assert_eq!(json["migrations"][0]["version"], 1);
150 assert_eq!(json["elapsed_ms"], 7);
151 }
152
153 #[test]
154 fn schema_object_renomeia_campo_type() {
155 let obj = SchemaObject {
156 name: "entities".to_string(),
157 object_type: "table".to_string(),
158 };
159 let json: Value = serde_json::to_value(&obj).unwrap();
160 assert!(json.get("object_type").is_none());
161 assert_eq!(json["type"], "table");
162 }
163
164 #[test]
165 fn migration_record_serializa_todos_campos() {
166 let rec = MigrationRecord {
167 version: 3,
168 name: "V003__indexes".to_string(),
169 applied_on: "2026-04-19T12:00:00Z".to_string(),
170 };
171 let json: Value = serde_json::to_value(&rec).unwrap();
172 assert_eq!(json["version"], 3);
173 assert_eq!(json["name"], "V003__indexes");
174 assert_eq!(json["applied_on"], "2026-04-19T12:00:00Z");
175 }
176}