1use quarb::{AstAdapter, NodeId, Value};
16use quarb_relational::{RelationalModel, RowSpec, TableSpec};
17use rusqlite::Connection;
18use rusqlite::types::ValueRef;
19
20#[derive(Debug, thiserror::Error)]
22pub enum SqliteError {
23 #[error("sqlite: {0}")]
24 Sqlite(#[from] rusqlite::Error),
25 #[error("pushdown plan: {0}")]
26 Plan(String),
27}
28
29pub struct SqliteAdapter {
31 model: RelationalModel,
32}
33
34fn to_value(v: ValueRef<'_>) -> Value {
35 match v {
36 ValueRef::Null => Value::Null,
37 ValueRef::Integer(n) => Value::Int(n),
38 ValueRef::Real(f) => Value::Float(f),
39 ValueRef::Text(t) => Value::Str(String::from_utf8_lossy(t).into_owned()),
40 ValueRef::Blob(b) => Value::Str(format!("<blob {} bytes>", b.len())),
41 }
42}
43
44fn quote_ident(name: &str) -> String {
48 format!("\"{}\"", name.replace('"', "\"\""))
49}
50
51fn specs(conn: &Connection, refs: &[(String, String)]) -> Result<Vec<TableSpec>, SqliteError> {
61 let named: Vec<(String, bool)> = conn
62 .prepare(
63 "SELECT name, type = 'view' FROM sqlite_master \
64 WHERE type IN ('table', 'view') \
65 AND name NOT LIKE 'sqlite_%' ORDER BY name",
66 )?
67 .query_map([], |r| Ok((r.get(0)?, r.get(1)?)))?
68 .collect::<Result<_, _>>()?;
69
70 let mut out = Vec::new();
71 for (name, is_view) in named {
72 let mut columns = Vec::new();
75 let mut pk_cols: Vec<(i64, usize)> = Vec::new();
76 {
77 let mut stmt = conn.prepare(&format!("PRAGMA table_info({})", quote_ident(&name)))?;
78 let mut rows = stmt.query([])?;
79 while let Some(r) = rows.next()? {
80 let col: String = r.get(1)?;
81 let pk: i64 = r.get(5)?;
82 if pk > 0 {
83 pk_cols.push((pk, columns.len()));
84 }
85 columns.push(col);
86 }
87 }
88 pk_cols.sort();
89 let pk = (pk_cols.len() == 1)
95 .then(|| pk_cols[0].1)
96 .or_else(|| (is_view && columns.len() == 1).then_some(0));
97
98 let mut fks = Vec::new();
101 {
102 let mut stmt =
103 conn.prepare(&format!("PRAGMA foreign_key_list({})", quote_ident(&name)))?;
104 let mut rows = stmt.query([])?;
105 while let Some(r) = rows.next()? {
106 let target: String = r.get(2)?;
107 let from: String = r.get(3)?;
108 let to: Option<String> = r.get(4)?;
109 if let Some(idx) = columns.iter().position(|c| *c == from) {
110 fks.push((idx, target, to.unwrap_or_default()));
111 }
112 }
113 }
114 for (field, container) in refs {
118 let col = match field.split_once('.') {
119 Some((table, col)) if table == name => col,
120 Some(_) => continue,
121 None => field.as_str(),
122 };
123 if let Some(idx) = columns.iter().position(|c| c == col)
124 && !fks.iter().any(|(i, _, _)| *i == idx)
125 && !(*container == name && pk == Some(idx))
128 {
129 fks.push((idx, container.clone(), String::new()));
130 }
131 }
132 out.push(TableSpec {
133 name,
134 columns,
135 pk,
136 fks,
137 });
138 }
139 Ok(out)
140}
141
142fn fetch_rows_where(
145 conn: &Connection,
146 spec: &TableSpec,
147 where_sql: Option<&str>,
148) -> Result<Vec<RowSpec>, SqliteError> {
149 let cols = spec
150 .columns
151 .iter()
152 .map(|c| quote_ident(c))
153 .collect::<Vec<_>>()
154 .join(", ");
155 let table = quote_ident(&spec.name);
156 let filter = match where_sql {
157 Some(w) => format!(" WHERE {w}"),
158 None => String::new(),
159 };
160 let with_rowid = format!("SELECT rowid, {cols} FROM {table}{filter} ORDER BY rowid");
166 if let Ok(mut stmt) = conn.prepare(&with_rowid) {
167 let mut rows = stmt.query([])?;
168 let mut out = Vec::new();
169 while let Some(r) = rows.next()? {
170 let rowid: i64 = r.get(0)?;
171 let values: Vec<Value> = (0..spec.columns.len())
172 .map(|i| to_value(r.get_ref(i + 1).expect("column in range")))
173 .collect();
174 out.push(RowSpec { rowid, values });
175 }
176 return Ok(out);
177 }
178 let order = match spec.pk {
182 Some(i) => quote_ident(&spec.columns[i]),
183 None => cols.clone(),
184 };
185 let mut stmt = conn.prepare(&format!(
186 "SELECT {cols} FROM {table}{filter} ORDER BY {order}"
187 ))?;
188 let mut rows = stmt.query([])?;
189 let mut out = Vec::new();
190 let mut rowid = 0i64;
191 while let Some(r) = rows.next()? {
192 rowid += 1;
193 let values: Vec<Value> = (0..spec.columns.len())
194 .map(|i| to_value(r.get_ref(i).expect("column in range")))
195 .collect();
196 out.push(RowSpec { rowid, values });
197 }
198 Ok(out)
199}
200
201impl SqliteAdapter {
202 pub fn open(path: &std::path::Path) -> Result<Self, SqliteError> {
206 Self::open_impl(path, None, &[])
207 }
208
209 pub fn open_with_refs(
213 path: &std::path::Path,
214 refs: &[(String, String)],
215 ) -> Result<Self, SqliteError> {
216 Self::open_impl(path, None, refs)
217 }
218
219 pub fn open_filtered(
222 path: &std::path::Path,
223 table: &str,
224 where_sql: &str,
225 ) -> Result<Self, SqliteError> {
226 Self::open_impl(path, Some((table.to_string(), where_sql.to_string())), &[])
227 }
228
229 pub fn open_filtered_with_refs(
231 path: &std::path::Path,
232 table: &str,
233 where_sql: &str,
234 refs: &[(String, String)],
235 ) -> Result<Self, SqliteError> {
236 Self::open_impl(path, Some((table.to_string(), where_sql.to_string())), refs)
237 }
238
239 fn open_impl(
240 path: &std::path::Path,
241 filter: Option<(String, String)>,
242 refs: &[(String, String)],
243 ) -> Result<Self, SqliteError> {
244 let conn = Connection::open_with_flags(path, rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY)?;
245 let specs = specs(&conn, refs)?;
246 let model = RelationalModel::lazy(
247 specs,
248 Box::new(move |_, spec| {
249 let w = filter
250 .as_ref()
251 .filter(|(t, _)| *t == spec.name)
252 .map(|(_, w)| w.as_str());
253 fetch_rows_where(&conn, spec, w).map_err(|e| e.to_string())
254 }),
255 );
256 Ok(SqliteAdapter { model })
257 }
258
259 pub fn from_bytes(bytes: &[u8]) -> Result<Self, SqliteError> {
264 let mut conn = Connection::open_in_memory()?;
265 conn.deserialize_read_exact(rusqlite::MAIN_DB, bytes, bytes.len(), true)?;
266 Self::load(&conn)
267 }
268
269 pub fn load(conn: &Connection) -> Result<Self, SqliteError> {
272 Self::load_with_refs(conn, &[])
273 }
274
275 pub fn load_with_refs(
277 conn: &Connection,
278 refs: &[(String, String)],
279 ) -> Result<Self, SqliteError> {
280 let mut input = Vec::new();
281 for spec in specs(conn, refs)? {
282 let rows = fetch_rows_where(conn, &spec, None)?;
283 input.push((spec, rows));
284 }
285 Ok(SqliteAdapter {
286 model: RelationalModel::build(input),
287 })
288 }
289
290 pub fn locator(&self, node: NodeId) -> String {
292 self.model.locator(node)
293 }
294}
295
296fn unique_key(conn: &Connection, table: &str, cols: &[String]) -> Result<bool, SqliteError> {
304 use std::collections::HashSet;
305 let want: HashSet<&str> = cols.iter().map(|s| s.as_str()).collect();
306 if want.is_empty() {
307 return Ok(false);
308 }
309 let mut stmt = conn.prepare(&format!("PRAGMA table_info({})", quote_ident(table)))?;
311 let mut pk: HashSet<String> = HashSet::new();
312 let mut rows = stmt.query([])?;
313 while let Some(r) = rows.next()? {
314 let name: String = r.get(1)?;
315 let ord: i64 = r.get(5)?;
316 if ord > 0 {
317 pk.insert(name);
318 }
319 }
320 if !pk.is_empty() && pk.iter().all(|c| want.contains(c.as_str())) {
321 return Ok(true);
322 }
323 let mut stmt = conn.prepare(&format!("PRAGMA index_list({})", quote_ident(table)))?;
325 let mut uniques: Vec<String> = Vec::new();
326 let mut rows = stmt.query([])?;
327 while let Some(r) = rows.next()? {
328 let name: String = r.get(1)?;
329 let is_unique: i64 = r.get(2)?;
330 let partial: i64 = r.get(4).unwrap_or(0);
331 if is_unique == 1 && partial == 0 {
332 uniques.push(name);
333 }
334 }
335 for idx in uniques {
336 let mut stmt = conn.prepare(&format!("PRAGMA index_info({})", quote_ident(&idx)))?;
337 let mut cols_of: Vec<String> = Vec::new();
338 let mut rows = stmt.query([])?;
339 while let Some(r) = rows.next()? {
340 cols_of.push(r.get(2)?);
341 }
342 if !cols_of.is_empty() && cols_of.iter().all(|c| want.contains(c.as_str())) {
343 return Ok(true);
344 }
345 }
346 Ok(false)
347}
348
349pub fn raw_query(
350 path: &std::path::Path,
351 sql: &str,
352 order_table: Option<&str>,
353 join_left: Option<(&str, &[String])>,
354) -> Result<(Vec<String>, Vec<Vec<Value>>), SqliteError> {
355 let conn = Connection::open_with_flags(path, rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY)?;
356 if let Some((table, cols)) = join_left
361 && !unique_key(&conn, table, cols)?
362 {
363 return Err(SqliteError::Plan(format!(
364 "join ON does not bind {table} by a unique key; \
365 the SQL JOIN would multiply rows"
366 )));
367 }
368 let sql = match order_table {
369 Some(t) => {
370 let key = specs(&conn, &[])?
371 .into_iter()
372 .find(|s| s.name == t)
373 .and_then(|s| s.pk.map(|i| s.columns[i].clone()))
374 .unwrap_or_else(|| "rowid".to_string());
375 format!("{sql} ORDER BY {t}.{key}")
376 }
377 None => sql.to_string(),
378 };
379 let mut stmt = conn.prepare(&sql)?;
380 let cols: Vec<String> = stmt.column_names().iter().map(|c| c.to_string()).collect();
381 let n = cols.len();
382 let mut rows = stmt.query([])?;
383 let mut out = Vec::new();
384 while let Some(r) = rows.next()? {
385 out.push(
386 (0..n)
387 .map(|i| to_value(r.get_ref(i).expect("column in range")))
388 .collect(),
389 );
390 }
391 Ok((cols, out))
392}
393
394impl AstAdapter for SqliteAdapter {
395 fn root(&self) -> NodeId {
396 self.model.root()
397 }
398 fn children(&self, node: NodeId) -> Vec<NodeId> {
399 self.model.children(node)
400 }
401 fn name(&self, node: NodeId) -> Option<String> {
402 self.model.name(node)
403 }
404 fn parent(&self, node: NodeId) -> Option<NodeId> {
405 self.model.parent(node)
406 }
407 fn property(&self, node: NodeId, name: &str) -> Option<Value> {
408 self.model.property(node, name)
409 }
410 fn default_value(&self, node: NodeId) -> Option<Value> {
411 self.model.default_value(node)
412 }
413 fn metadata(&self, node: NodeId, key: &str) -> Option<Value> {
414 self.model.metadata(node, key)
415 }
416 fn resolve(&self, node: NodeId, property: &str, hint: Option<&str>) -> Option<NodeId> {
417 self.model.resolve(node, property, hint)
418 }
419 fn links(&self, node: NodeId) -> Vec<(String, NodeId)> {
420 self.model.links(node)
421 }
422 fn backlinks(&self, node: NodeId) -> Vec<(String, NodeId)> {
423 self.model.backlinks(node)
424 }
425}