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