1use rusqlite::Connection;
2use serde::Serialize;
3use std::io::Write;
4use std::path::Path;
5
6pub struct Database {
8 conn: Connection,
9 path: String,
10}
11
12#[derive(Debug, Clone, Serialize)]
13pub struct DbInfo {
14 pub path: String,
15 pub file_size: u64,
16 pub sqlite_version: String,
17 pub page_count: u64,
18 pub page_size: u64,
19 pub table_count: usize,
20}
21
22#[derive(Debug, Clone, Serialize)]
23pub struct TableInfo {
24 pub name: String,
25 pub row_count: u64,
26 pub column_count: usize,
27}
28
29#[derive(Debug, Clone, Serialize)]
30pub struct ColumnInfo {
31 pub name: String,
32 pub col_type: String,
33 pub nullable: bool,
34 pub primary_key: bool,
35 pub default_value: Option<String>,
36}
37
38#[derive(Debug, Clone, Serialize)]
39pub struct IndexInfo {
40 pub name: String,
41 pub table_name: String,
42 pub unique: bool,
43 pub columns: Vec<String>,
44}
45
46#[derive(Debug, Clone, Serialize)]
47pub struct QueryResult {
48 pub columns: Vec<String>,
49 pub rows: Vec<Vec<CellValue>>,
50 pub total_rows: Option<u64>,
51}
52
53#[derive(Debug, Clone, Serialize)]
54#[serde(untagged)]
55pub enum CellValue {
56 Null,
57 Integer(i64),
58 Real(f64),
59 Text(String),
60 Blob(Vec<u8>),
61}
62
63impl std::fmt::Display for CellValue {
64 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65 match self {
66 CellValue::Null => write!(f, "NULL"),
67 CellValue::Integer(i) => write!(f, "{}", i),
68 CellValue::Real(r) => write!(f, "{}", r),
69 CellValue::Text(s) => write!(f, "{}", s),
70 CellValue::Blob(b) => write!(f, "<blob {} bytes>", b.len()),
71 }
72 }
73}
74
75#[derive(Debug, Clone, Serialize)]
76pub struct Sort {
77 pub column: String,
78 pub ascending: bool,
79}
80
81impl Database {
82 pub fn open(path: &str) -> Result<Self, String> {
83 let conn = Connection::open_with_flags(
84 path,
85 rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY | rusqlite::OpenFlags::SQLITE_OPEN_NO_MUTEX,
86 )
87 .map_err(|e| format!("Failed to open database: {}", e))?;
88 Ok(Database {
89 conn,
90 path: path.to_string(),
91 })
92 }
93
94 pub fn path(&self) -> &str {
95 &self.path
96 }
97
98 pub fn get_info(&self) -> Result<DbInfo, String> {
99 let sqlite_version: String = self
100 .conn
101 .query_row("SELECT sqlite_version()", [], |row| row.get(0))
102 .map_err(|e| e.to_string())?;
103
104 let page_count: u64 = self
105 .conn
106 .pragma_query_value(None, "page_count", |row| row.get(0))
107 .map_err(|e| e.to_string())?;
108
109 let page_size: u64 = self
110 .conn
111 .pragma_query_value(None, "page_size", |row| row.get(0))
112 .map_err(|e| e.to_string())?;
113
114 let file_size = Path::new(&self.path)
115 .metadata()
116 .map(|m| m.len())
117 .unwrap_or(0);
118
119 let tables = self.list_tables().map_err(|e| e.to_string())?;
120
121 Ok(DbInfo {
122 path: self.path.clone(),
123 file_size,
124 sqlite_version,
125 page_count,
126 page_size,
127 table_count: tables.len(),
128 })
129 }
130
131 pub fn list_tables(&self) -> Result<Vec<TableInfo>, String> {
132 let mut stmt = self
133 .conn
134 .prepare(
135 "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name",
136 )
137 .map_err(|e| e.to_string())?;
138
139 let names: Vec<String> = stmt
140 .query_map([], |row| row.get(0))
141 .map_err(|e| e.to_string())?
142 .filter_map(|r| r.ok())
143 .collect();
144
145 let mut tables = Vec::new();
146 for name in names {
147 let row_count = self.get_row_count(&name).unwrap_or(0);
148 let columns = self.get_schema(&name).unwrap_or_default();
149 tables.push(TableInfo {
150 name,
151 row_count,
152 column_count: columns.len(),
153 });
154 }
155 Ok(tables)
156 }
157
158 pub fn list_views(&self) -> Result<Vec<String>, String> {
159 let mut stmt = self
160 .conn
161 .prepare("SELECT name FROM sqlite_master WHERE type='view' ORDER BY name")
162 .map_err(|e| e.to_string())?;
163
164 let views = stmt
165 .query_map([], |row| row.get(0))
166 .map_err(|e| e.to_string())?
167 .filter_map(|r| r.ok())
168 .collect();
169
170 Ok(views)
171 }
172
173 pub fn list_indexes(&self) -> Result<Vec<IndexInfo>, String> {
174 let mut stmt = self
175 .conn
176 .prepare(
177 "SELECT name, tbl_name FROM sqlite_master WHERE type='index' AND name NOT LIKE 'sqlite_%' ORDER BY name",
178 )
179 .map_err(|e| e.to_string())?;
180
181 let raw: Vec<(String, String)> = stmt
182 .query_map([], |row| Ok((row.get(0)?, row.get(1)?)))
183 .map_err(|e| e.to_string())?
184 .filter_map(|r| r.ok())
185 .collect();
186
187 let mut indexes = Vec::new();
188 for (name, table_name) in raw {
189 let mut info_stmt = self
190 .conn
191 .prepare(&format!("PRAGMA index_info(\"{}\")", name))
192 .map_err(|e| e.to_string())?;
193
194 let columns: Vec<String> = info_stmt
195 .query_map([], |row| row.get(2))
196 .map_err(|e| e.to_string())?
197 .filter_map(|r| r.ok())
198 .collect();
199
200 let unique = self
201 .conn
202 .prepare(&format!("PRAGMA index_list(\"{}\")", table_name))
203 .and_then(|mut s| {
204 let mut found = false;
205 let rows = s.query_map([], |row| {
206 let idx_name: String = row.get(1)?;
207 let is_unique: bool = row.get(2)?;
208 Ok((idx_name, is_unique))
209 })?;
210 for r in rows.flatten() {
211 if r.0 == name {
212 found = r.1;
213 break;
214 }
215 }
216 Ok(found)
217 })
218 .unwrap_or(false);
219
220 indexes.push(IndexInfo {
221 name,
222 table_name,
223 unique,
224 columns,
225 });
226 }
227 Ok(indexes)
228 }
229
230 pub fn get_schema(&self, table: &str) -> Result<Vec<ColumnInfo>, String> {
231 let mut stmt = self
232 .conn
233 .prepare(&format!("PRAGMA table_info(\"{}\")", table))
234 .map_err(|e| e.to_string())?;
235
236 let columns = stmt
237 .query_map([], |row| {
238 Ok(ColumnInfo {
239 name: row.get(1)?,
240 col_type: row.get::<_, String>(2).unwrap_or_default(),
241 nullable: !row.get::<_, bool>(3).unwrap_or(false),
242 primary_key: row.get::<_, bool>(5).unwrap_or(false),
243 default_value: row.get(4).ok(),
244 })
245 })
246 .map_err(|e| e.to_string())?
247 .filter_map(|r| r.ok())
248 .collect();
249
250 Ok(columns)
251 }
252
253 pub fn query_table(
254 &self,
255 table: &str,
256 limit: usize,
257 offset: usize,
258 sort: Option<Sort>,
259 ) -> Result<QueryResult, String> {
260 let order_clause = match &sort {
261 Some(s) => format!(
262 " ORDER BY \"{}\" {}",
263 s.column,
264 if s.ascending { "ASC" } else { "DESC" }
265 ),
266 None => String::new(),
267 };
268
269 let sql = format!(
270 "SELECT * FROM \"{}\"{} LIMIT {} OFFSET {}",
271 table, order_clause, limit, offset
272 );
273
274 let total = self.get_row_count(table).ok();
275 let mut result = self.run_query(&sql)?;
276 result.total_rows = total;
277 Ok(result)
278 }
279
280 pub fn run_query(&self, sql: &str) -> Result<QueryResult, String> {
281 let mut stmt = self.conn.prepare(sql).map_err(|e| e.to_string())?;
282
283 let columns: Vec<String> = stmt
284 .column_names()
285 .iter()
286 .map(|s| s.to_string())
287 .collect();
288
289 let rows: Vec<Vec<CellValue>> = stmt
290 .query_map([], |row| {
291 let mut cells = Vec::new();
292 for i in 0..columns.len() {
293 let val = match row.get_ref(i) {
294 Ok(rusqlite::types::ValueRef::Null) => CellValue::Null,
295 Ok(rusqlite::types::ValueRef::Integer(n)) => CellValue::Integer(n),
296 Ok(rusqlite::types::ValueRef::Real(f)) => CellValue::Real(f),
297 Ok(rusqlite::types::ValueRef::Text(s)) => {
298 CellValue::Text(String::from_utf8_lossy(s).to_string())
299 }
300 Ok(rusqlite::types::ValueRef::Blob(b)) => CellValue::Blob(b.to_vec()),
301 Err(_) => CellValue::Null,
302 };
303 cells.push(val);
304 }
305 Ok(cells)
306 })
307 .map_err(|e| e.to_string())?
308 .filter_map(|r| r.ok())
309 .collect();
310
311 Ok(QueryResult {
312 columns,
313 rows,
314 total_rows: None,
315 })
316 }
317
318 pub fn get_row_count(&self, table: &str) -> Result<u64, String> {
319 self.conn
320 .query_row(
321 &format!("SELECT COUNT(*) FROM \"{}\"", table),
322 [],
323 |row| row.get(0),
324 )
325 .map_err(|e| e.to_string())
326 }
327
328 pub fn export_csv<W: Write>(&self, table: &str, writer: &mut W) -> Result<(), String> {
329 let result = self.run_query(&format!("SELECT * FROM \"{}\"", table))?;
330
331 let header = result
333 .columns
334 .iter()
335 .map(|c| escape_csv(c))
336 .collect::<Vec<_>>()
337 .join(",");
338 writeln!(writer, "{}", header).map_err(|e| e.to_string())?;
339
340 for row in &result.rows {
342 let line = row
343 .iter()
344 .map(|v| escape_csv(&v.to_string()))
345 .collect::<Vec<_>>()
346 .join(",");
347 writeln!(writer, "{}", line).map_err(|e| e.to_string())?;
348 }
349
350 Ok(())
351 }
352}
353
354fn escape_csv(s: &str) -> String {
355 if s.contains(',') || s.contains('"') || s.contains('\n') {
356 format!("\"{}\"", s.replace('"', "\"\""))
357 } else {
358 s.to_string()
359 }
360}
361
362pub fn format_size(bytes: u64) -> String {
363 if bytes < 1024 {
364 format!("{} B", bytes)
365 } else if bytes < 1024 * 1024 {
366 format!("{:.1} KB", bytes as f64 / 1024.0)
367 } else if bytes < 1024 * 1024 * 1024 {
368 format!("{:.1} MB", bytes as f64 / (1024.0 * 1024.0))
369 } else {
370 format!("{:.2} GB", bytes as f64 / (1024.0 * 1024.0 * 1024.0))
371 }
372}