Skip to main content

dbiewlite_core/
sqlite.rs

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}