1use rusqlite::Connection;
2use std::io::Write;
3use std::path::Path;
4
5use crate::types::*;
6
7pub struct SqliteBackend {
8 conn: Connection,
9 path: String,
10}
11
12impl SqliteBackend {
13 pub fn open(path: &str) -> Result<Self, String> {
14 let conn = Connection::open_with_flags(
15 path,
16 rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY | rusqlite::OpenFlags::SQLITE_OPEN_NO_MUTEX,
17 )
18 .map_err(|e| format!("Failed to open database: {}", e))?;
19 Ok(SqliteBackend {
20 conn,
21 path: path.to_string(),
22 })
23 }
24
25 pub fn path(&self) -> &str {
26 &self.path
27 }
28
29 pub fn get_info(&self) -> Result<DbInfo, String> {
30 let sqlite_version: String = self
31 .conn
32 .query_row("SELECT sqlite_version()", [], |row| row.get(0))
33 .map_err(|e| e.to_string())?;
34
35 let page_count: u64 = self
36 .conn
37 .pragma_query_value(None, "page_count", |row| row.get(0))
38 .map_err(|e| e.to_string())?;
39
40 let page_size: u64 = self
41 .conn
42 .pragma_query_value(None, "page_size", |row| row.get(0))
43 .map_err(|e| e.to_string())?;
44
45 let file_size = Path::new(&self.path)
46 .metadata()
47 .map(|m| m.len())
48 .unwrap_or(0);
49
50 let tables = self.list_tables().map_err(|e| e.to_string())?;
51
52 Ok(DbInfo {
53 path: self.path.clone(),
54 file_size,
55 engine: "SQLite".to_string(),
56 engine_version: sqlite_version,
57 page_count: Some(page_count),
58 page_size: Some(page_size),
59 table_count: tables.len(),
60 })
61 }
62
63 pub fn list_tables(&self) -> Result<Vec<TableInfo>, String> {
64 let mut stmt = self
65 .conn
66 .prepare(
67 "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name",
68 )
69 .map_err(|e| e.to_string())?;
70
71 let names: Vec<String> = stmt
72 .query_map([], |row| row.get(0))
73 .map_err(|e| e.to_string())?
74 .filter_map(|r| r.ok())
75 .collect();
76
77 let mut tables = Vec::new();
78 for name in names {
79 let row_count = self.get_row_count(&name).unwrap_or(0);
80 let columns = self.get_schema(&name).unwrap_or_default();
81 tables.push(TableInfo {
82 name,
83 row_count,
84 column_count: columns.len(),
85 });
86 }
87 Ok(tables)
88 }
89
90 pub fn list_views(&self) -> Result<Vec<String>, String> {
91 let mut stmt = self
92 .conn
93 .prepare("SELECT name FROM sqlite_master WHERE type='view' ORDER BY name")
94 .map_err(|e| e.to_string())?;
95
96 let views = stmt
97 .query_map([], |row| row.get(0))
98 .map_err(|e| e.to_string())?
99 .filter_map(|r| r.ok())
100 .collect();
101
102 Ok(views)
103 }
104
105 pub fn list_indexes(&self) -> Result<Vec<IndexInfo>, String> {
106 let mut stmt = self
107 .conn
108 .prepare(
109 "SELECT name, tbl_name FROM sqlite_master WHERE type='index' AND name NOT LIKE 'sqlite_%' ORDER BY name",
110 )
111 .map_err(|e| e.to_string())?;
112
113 let raw: Vec<(String, String)> = stmt
114 .query_map([], |row| Ok((row.get(0)?, row.get(1)?)))
115 .map_err(|e| e.to_string())?
116 .filter_map(|r| r.ok())
117 .collect();
118
119 let mut indexes = Vec::new();
120 for (name, table_name) in raw {
121 let mut info_stmt = self
122 .conn
123 .prepare(&format!("PRAGMA index_info(\"{}\")", name))
124 .map_err(|e| e.to_string())?;
125
126 let columns: Vec<String> = info_stmt
127 .query_map([], |row| row.get(2))
128 .map_err(|e| e.to_string())?
129 .filter_map(|r| r.ok())
130 .collect();
131
132 let unique = self
133 .conn
134 .prepare(&format!("PRAGMA index_list(\"{}\")", table_name))
135 .and_then(|mut s| {
136 let mut found = false;
137 let rows = s.query_map([], |row| {
138 let idx_name: String = row.get(1)?;
139 let is_unique: bool = row.get(2)?;
140 Ok((idx_name, is_unique))
141 })?;
142 for r in rows.flatten() {
143 if r.0 == name {
144 found = r.1;
145 break;
146 }
147 }
148 Ok(found)
149 })
150 .unwrap_or(false);
151
152 indexes.push(IndexInfo {
153 name,
154 table_name,
155 unique,
156 columns,
157 });
158 }
159 Ok(indexes)
160 }
161
162 pub fn get_schema(&self, table: &str) -> Result<Vec<ColumnInfo>, String> {
163 let mut stmt = self
164 .conn
165 .prepare(&format!("PRAGMA table_info(\"{}\")", table))
166 .map_err(|e| e.to_string())?;
167
168 let columns = stmt
169 .query_map([], |row| {
170 Ok(ColumnInfo {
171 name: row.get(1)?,
172 col_type: row.get::<_, String>(2).unwrap_or_default(),
173 nullable: !row.get::<_, bool>(3).unwrap_or(false),
174 primary_key: row.get::<_, bool>(5).unwrap_or(false),
175 default_value: row.get(4).ok(),
176 })
177 })
178 .map_err(|e| e.to_string())?
179 .filter_map(|r| r.ok())
180 .collect();
181
182 Ok(columns)
183 }
184
185 pub fn query_table(
186 &self,
187 table: &str,
188 limit: usize,
189 offset: usize,
190 sort: Option<Sort>,
191 ) -> Result<QueryResult, String> {
192 let order_clause = match &sort {
193 Some(s) => format!(
194 " ORDER BY \"{}\" {}",
195 s.column,
196 if s.ascending { "ASC" } else { "DESC" }
197 ),
198 None => String::new(),
199 };
200
201 let sql = format!(
202 "SELECT * FROM \"{}\"{} LIMIT {} OFFSET {}",
203 table, order_clause, limit, offset
204 );
205
206 let total = self.get_row_count(table).ok();
207 let mut result = self.run_query(&sql)?;
208 result.total_rows = total;
209 Ok(result)
210 }
211
212 pub fn run_query(&self, sql: &str) -> Result<QueryResult, String> {
213 let mut stmt = self.conn.prepare(sql).map_err(|e| e.to_string())?;
214
215 let columns: Vec<String> = stmt
216 .column_names()
217 .iter()
218 .map(|s| s.to_string())
219 .collect();
220
221 let rows: Vec<Vec<CellValue>> = stmt
222 .query_map([], |row| {
223 let mut cells = Vec::new();
224 for i in 0..columns.len() {
225 let val = match row.get_ref(i) {
226 Ok(rusqlite::types::ValueRef::Null) => CellValue::Null,
227 Ok(rusqlite::types::ValueRef::Integer(n)) => CellValue::Integer(n),
228 Ok(rusqlite::types::ValueRef::Real(f)) => CellValue::Real(f),
229 Ok(rusqlite::types::ValueRef::Text(s)) => {
230 CellValue::Text(String::from_utf8_lossy(s).to_string())
231 }
232 Ok(rusqlite::types::ValueRef::Blob(b)) => CellValue::Blob(b.to_vec()),
233 Err(_) => CellValue::Null,
234 };
235 cells.push(val);
236 }
237 Ok(cells)
238 })
239 .map_err(|e| e.to_string())?
240 .filter_map(|r| r.ok())
241 .collect();
242
243 Ok(QueryResult {
244 columns,
245 rows,
246 total_rows: None,
247 })
248 }
249
250 pub fn get_row_count(&self, table: &str) -> Result<u64, String> {
251 self.conn
252 .query_row(
253 &format!("SELECT COUNT(*) FROM \"{}\"", table),
254 [],
255 |row| row.get(0),
256 )
257 .map_err(|e| e.to_string())
258 }
259
260 pub fn export_csv<W: Write>(&self, table: &str, writer: &mut W) -> Result<(), String> {
261 let result = self.run_query(&format!("SELECT * FROM \"{}\"", table))?;
262 write_csv(&result, writer)
263 }
264}