Skip to main content

dbiewlite_core/
sqlite.rs

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}