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