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 quarb_relational::quote_double(name)
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 {
175 rowid: Some(rowid),
176 values,
177 });
178 }
179 return Ok(out);
180 }
181 let order = match spec.pk {
185 Some(i) => quote_ident(&spec.columns[i]),
186 None => cols.clone(),
187 };
188 let mut stmt = conn.prepare(&format!(
189 "SELECT {cols} FROM {table}{filter} ORDER BY {order}"
190 ))?;
191 let mut rows = stmt.query([])?;
192 let mut out = Vec::new();
193 let mut rowid = 0i64;
194 while let Some(r) = rows.next()? {
195 rowid += 1;
196 let values: Vec<Value> = (0..spec.columns.len())
197 .map(|i| to_value(r.get_ref(i).expect("column in range")))
198 .collect();
199 out.push(RowSpec {
200 rowid: Some(rowid),
201 values,
202 });
203 }
204 Ok(out)
205}
206
207fn fetch_rows_keyed(
218 conn: &Connection,
219 spec: &TableSpec,
220 filter: Option<&str>,
221 col: Option<usize>,
222 values: &[Value],
223) -> Result<Vec<RowSpec>, String> {
224 use rusqlite::types::Value as Sql;
225 let column = match col.or(spec.pk) {
226 Some(i) => quote_ident(&spec.columns[i]),
227 None => "rowid".to_string(),
228 };
229 let mut binds: Vec<Sql> = Vec::new();
230 for v in values {
231 match v {
232 Value::Int(i) => {
233 binds.push(Sql::Integer(*i));
234 binds.push(Sql::Text(i.to_string()));
235 }
236 Value::Float(f) => {
237 binds.push(Sql::Real(*f));
238 binds.push(Sql::Text(v.to_string()));
239 }
240 Value::Bool(b) => binds.push(Sql::Integer(*b as i64)),
241 Value::Null => {}
242 other => {
243 let text = other.to_string();
244 if let Ok(i) = text.parse::<i64>() {
245 binds.push(Sql::Integer(i));
246 } else if let Ok(f) = text.parse::<f64>() {
247 binds.push(Sql::Real(f));
248 }
249 binds.push(Sql::Text(text));
250 }
251 }
252 }
253 if binds.is_empty() {
254 return Ok(Vec::new());
255 }
256 let cols = spec
257 .columns
258 .iter()
259 .map(|c| quote_ident(c))
260 .collect::<Vec<_>>()
261 .join(", ");
262 let marks = vec!["?"; binds.len()].join(", ");
263 let filter = match filter {
264 Some(w) => format!("({w}) AND "),
265 None => String::new(),
266 };
267 let sql = format!(
268 "SELECT rowid, {cols} FROM {} WHERE {filter}{column} IN ({marks}) ORDER BY rowid",
269 quote_ident(&spec.name)
270 );
271 let mut stmt = conn.prepare(&sql).map_err(|e| e.to_string())?;
272 let mut rows = stmt
273 .query(rusqlite::params_from_iter(binds.iter()))
274 .map_err(|e| e.to_string())?;
275 let mut out = Vec::new();
276 while let Some(r) = rows.next().map_err(|e| e.to_string())? {
277 let rowid: i64 = r.get(0).map_err(|e| e.to_string())?;
278 let values: Vec<Value> = (0..spec.columns.len())
279 .map(|i| to_value(r.get_ref(i + 1).expect("column in range")))
280 .collect();
281 out.push(RowSpec {
282 rowid: Some(rowid),
283 values,
284 });
285 }
286 Ok(out)
287}
288
289fn index_rows_per_key(conn: &Connection, table: &str, column: &str) -> Result<u64, String> {
294 let mut list = conn
295 .prepare(&format!("PRAGMA index_list({})", quote_ident(table)))
296 .map_err(|e| e.to_string())?;
297 let names: Vec<String> = list
298 .query_map([], |r| r.get::<_, String>(1))
299 .map_err(|e| e.to_string())?
300 .filter_map(Result::ok)
301 .collect();
302 for idx in names {
303 let first: Option<String> = conn
304 .query_row(
305 &format!("PRAGMA index_info({})", quote_ident(&idx)),
306 [],
307 |r| r.get(2),
308 )
309 .ok();
310 if first.as_deref() != Some(column) {
311 continue;
312 }
313 let stat: String = conn
314 .query_row(
315 "SELECT stat FROM sqlite_stat1 WHERE tbl = ?1 AND idx = ?2",
316 [table, idx.as_str()],
317 |r| r.get(0),
318 )
319 .map_err(|e| e.to_string())?;
320 return stat
321 .split_whitespace()
322 .nth(1)
323 .and_then(|t| t.parse().ok())
324 .ok_or_else(|| "no per-key figure in sqlite_stat1".to_string());
325 }
326 Err(format!("no index opens with {column}"))
327}
328
329impl SqliteAdapter {
330 pub fn open(path: &std::path::Path) -> Result<Self, SqliteError> {
334 Self::open_impl(path, None, &[])
335 }
336
337 pub fn open_with_refs(
341 path: &std::path::Path,
342 refs: &[(String, String)],
343 ) -> Result<Self, SqliteError> {
344 Self::open_impl(path, None, refs)
345 }
346
347 pub fn open_filtered(
350 path: &std::path::Path,
351 table: &str,
352 where_sql: &str,
353 ) -> Result<Self, SqliteError> {
354 Self::open_impl(path, Some((table.to_string(), where_sql.to_string())), &[])
355 }
356
357 pub fn open_filtered_with_refs(
359 path: &std::path::Path,
360 table: &str,
361 where_sql: &str,
362 refs: &[(String, String)],
363 ) -> Result<Self, SqliteError> {
364 Self::open_impl(path, Some((table.to_string(), where_sql.to_string())), refs)
365 }
366
367 fn open_impl(
368 path: &std::path::Path,
369 filter: Option<(String, String)>,
370 refs: &[(String, String)],
371 ) -> Result<Self, SqliteError> {
372 let conn = std::rc::Rc::new(Connection::open_with_flags(
373 path,
374 rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY,
375 )?);
376 let specs = specs(&conn, refs)?;
377 let (whole, keyed, stats, listed) = (conn.clone(), conn.clone(), conn.clone(), conn);
378 let mut model = RelationalModel::lazy(
379 specs,
380 Box::new(move |_, spec| {
381 fetch_rows_where(&whole, spec, None).map_err(|e| e.to_string())
382 }),
383 )
384 .with_key_fetcher(Box::new(move |_, spec, col, values| {
386 fetch_rows_keyed(&keyed, spec, None, col, values)
387 }))
388 .with_estimator(std::rc::Rc::new(move |_, spec, equals| {
395 let stat: String = stats
396 .query_row(
397 "SELECT stat FROM sqlite_stat1 WHERE tbl = ?1 \
398 ORDER BY idx IS NULL DESC LIMIT 1",
399 [spec.name.as_str()],
400 |r| r.get(0),
401 )
402 .map_err(|e| e.to_string())?;
403 let n: u64 = stat
404 .split_whitespace()
405 .next()
406 .and_then(|t| t.parse().ok())
407 .ok_or("no row count in sqlite_stat1")?;
408 let mut per_key: Option<u64> = None;
413 for (col, _) in equals {
414 let column = &spec.columns[*col];
415 if let Ok(k) = index_rows_per_key(&stats, &spec.name, column) {
416 per_key = Some(per_key.map_or(k, |p| p.min(k)));
417 }
418 }
419 Ok(quarb::Estimate {
420 nodes: match (equals.is_empty(), per_key) {
421 (true, _) => (n, n),
422 (false, Some(k)) => (0, k.min(n)),
423 (false, None) => (0, n),
424 },
425 bytes: None,
426 requests: Some(0),
427 latency: quarb::Latency::Local,
428 confidence: quarb::Confidence::Guess,
429 })
430 }));
431 if let Some((table, where_sql)) = filter {
434 model = model.with_filtered_listing(
435 &table,
436 Box::new(move |_, spec| {
437 fetch_rows_where(&listed, spec, Some(&where_sql)).map_err(|e| e.to_string())
438 }),
439 );
440 }
441 Ok(SqliteAdapter { model })
442 }
443
444 pub fn from_bytes(bytes: &[u8]) -> Result<Self, SqliteError> {
449 let mut conn = Connection::open_in_memory()?;
450 conn.deserialize_read_exact(rusqlite::MAIN_DB, bytes, bytes.len(), true)?;
451 Self::load(&conn)
452 }
453
454 pub fn load(conn: &Connection) -> Result<Self, SqliteError> {
457 Self::load_with_refs(conn, &[])
458 }
459
460 pub fn load_with_refs(
462 conn: &Connection,
463 refs: &[(String, String)],
464 ) -> Result<Self, SqliteError> {
465 let mut input = Vec::new();
466 for spec in specs(conn, refs)? {
467 let rows = fetch_rows_where(conn, &spec, None)?;
468 input.push((spec, rows));
469 }
470 Ok(SqliteAdapter {
471 model: RelationalModel::build(input),
472 })
473 }
474
475 pub fn prefetch(&self, table: &str) -> Result<(), String> {
480 self.model.prefetch(table)
481 }
482
483 pub fn locator(&self, node: NodeId) -> String {
484 self.model.locator(node)
485 }
486}
487
488fn unique_key(conn: &Connection, table: &str, cols: &[String]) -> Result<bool, SqliteError> {
496 use std::collections::HashSet;
497 let want: HashSet<&str> = cols.iter().map(|s| s.as_str()).collect();
498 if want.is_empty() {
499 return Ok(false);
500 }
501 let mut stmt = conn.prepare(&format!("PRAGMA table_info({})", quote_ident(table)))?;
503 let mut pk: HashSet<String> = HashSet::new();
504 let mut rows = stmt.query([])?;
505 while let Some(r) = rows.next()? {
506 let name: String = r.get(1)?;
507 let ord: i64 = r.get(5)?;
508 if ord > 0 {
509 pk.insert(name);
510 }
511 }
512 if !pk.is_empty() && pk.iter().all(|c| want.contains(c.as_str())) {
513 return Ok(true);
514 }
515 let mut stmt = conn.prepare(&format!("PRAGMA index_list({})", quote_ident(table)))?;
517 let mut uniques: Vec<String> = Vec::new();
518 let mut rows = stmt.query([])?;
519 while let Some(r) = rows.next()? {
520 let name: String = r.get(1)?;
521 let is_unique: i64 = r.get(2)?;
522 let partial: i64 = r.get(4).unwrap_or(0);
523 if is_unique == 1 && partial == 0 {
524 uniques.push(name);
525 }
526 }
527 for idx in uniques {
528 let mut stmt = conn.prepare(&format!("PRAGMA index_info({})", quote_ident(&idx)))?;
529 let mut cols_of: Vec<String> = Vec::new();
530 let mut rows = stmt.query([])?;
531 while let Some(r) = rows.next()? {
532 cols_of.push(r.get(2)?);
533 }
534 if !cols_of.is_empty() && cols_of.iter().all(|c| want.contains(c.as_str())) {
535 return Ok(true);
536 }
537 }
538 Ok(false)
539}
540
541pub fn column_catalog(
550 path: &std::path::Path,
551 tables: &[&str],
552) -> Result<Vec<(String, bool)>, SqliteError> {
553 let conn = Connection::open_with_flags(path, rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY)?;
554 let mut catalog = Vec::new();
555 for table in tables {
556 let mut stmt = conn.prepare(&format!("PRAGMA table_info({})", quote_ident(table)))?;
557 let rows = stmt.query_map([], |r| Ok((r.get::<_, String>(1)?, r.get::<_, String>(2)?)))?;
558 for row in rows {
559 let (name, declared) = row?;
560 catalog.push((name, numeric_affinity(&declared)));
561 }
562 }
563 Ok(catalog)
564}
565
566fn numeric_affinity(declared: &str) -> bool {
569 let d = declared.to_ascii_uppercase();
570 if d.contains("INT") {
571 true
572 } else if d.contains("CHAR") || d.contains("CLOB") || d.contains("TEXT") {
573 false
574 } else {
575 !(d.contains("BLOB") || d.trim().is_empty())
576 }
577}
578
579pub fn raw_query(
580 path: &std::path::Path,
581 sql: &str,
582 order_table: Option<&str>,
583 join_left: Option<(&str, &[String])>,
584) -> Result<(Vec<String>, Vec<Vec<Value>>), SqliteError> {
585 let conn = Connection::open_with_flags(path, rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY)?;
586 if let Some((table, cols)) = join_left
591 && !unique_key(&conn, table, cols)?
592 {
593 return Err(SqliteError::Plan(format!(
594 "join ON does not bind {table} by a unique key; \
595 the SQL JOIN would multiply rows"
596 )));
597 }
598 let sql = match order_table {
599 Some(t) => {
600 let key = specs(&conn, &[])?
601 .into_iter()
602 .find(|s| s.name == t)
603 .and_then(|s| s.pk.map(|i| s.columns[i].clone()))
604 .unwrap_or_else(|| "rowid".to_string());
605 format!("{sql} ORDER BY {t}.{key}")
606 }
607 None => sql.to_string(),
608 };
609 quarb_relational::record_executed(&sql);
613 let mut stmt = conn.prepare(&sql)?;
614 let cols: Vec<String> = stmt.column_names().iter().map(|c| c.to_string()).collect();
615 let n = cols.len();
616 let mut rows = stmt.query([])?;
617 let mut out = Vec::new();
618 while let Some(r) = rows.next()? {
619 out.push(
620 (0..n)
621 .map(|i| to_value(r.get_ref(i).expect("column in range")))
622 .collect(),
623 );
624 }
625 Ok((cols, out))
626}
627
628impl AstAdapter for SqliteAdapter {
629 quarb::forward_ast_adapter!(model);
632}