Skip to main content

quarb_sqlite/
lib.rs

1//! SQLite adapter for the Quarb query engine.
2//!
3//! A thin catalog driver over the shared relational model
4//! (`quarb-relational`): it introspects the schema via PRAGMAs
5//! (`table_info`, `foreign_key_list`), streams every user table,
6//! maps SQLite's storage classes to values (`NULL` → null,
7//! `INTEGER`/`REAL` → numbers, text as itself, blobs as a size
8//! placeholder), and hands the result to [`RelationalModel`].
9//!
10//! The arbor mapping, the foreign-key reference machinery (`-->`
11//! chains, `->`/`<-` crosslinks, `<--` reverse resolution, the
12//! table-naming hint), and the metadata surface are documented on
13//! the shared model.
14
15use quarb::{AstAdapter, NodeId, Value};
16use quarb_relational::{RelationalModel, RowSpec, TableSpec};
17use rusqlite::Connection;
18use rusqlite::types::ValueRef;
19
20/// An error loading a database.
21#[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
29/// A SQLite database, materialized as an arbor.
30pub 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
44/// Quote an identifier (table or column name) for interpolation into
45/// SQL: wrap in double quotes, doubling any embedded quote, so
46/// reserved words and special characters survive.
47fn quote_ident(name: &str) -> String {
48    quarb_relational::quote_double(name)
49}
50
51/// Introspect the schema: every user table's and view's spec, via
52/// PRAGMAs. Views are containers like tables — a single-column view
53/// (the `SELECT DISTINCT` dimension idiom) is named by that column,
54/// the way a single-column primary key names a table's rows.
55/// `refs` is the declared-references document: each `(field,
56/// container)` pair synthesizes a foreign-key edge from every
57/// matching column (bare `column`, or `table.column` to scope) to
58/// the target container's key, unless the schema already declares
59/// one there.
60fn 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        // Columns and the primary key (a single-column pk names the
73        // rows; a composite or absent pk falls back to rowid).
74        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        // A single-column primary key names the rows. A view has no
90        // primary key ever; when it projects exactly one column —
91        // the `SELECT DISTINCT` dimension idiom — that column is
92        // the row's identity, so it names the rows (and keys `-->`
93        // resolution into the view).
94        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        // Declared foreign keys (an omitted target column means the
99        // target's primary key).
100        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        // Declared client-side references: synthesize an edge to the
115        // target container's key for every matching column the
116        // schema does not already cover.
117        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                // A container's key column pointing at the container
126                // itself is the identity, not an edge.
127                && !(*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
142/// Stream one table's rows, optionally filtered (partial
143/// pushdown: the engine re-applies the pushed predicates).
144fn 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    // Prefer SQLite's rowid: stable row identity, and the row's edge
161    // name for tables without a single-column primary key. WITHOUT
162    // ROWID tables have no rowid column, so `SELECT rowid` fails to
163    // prepare; fall back to a positional ordinal (below), ordered by
164    // the key for determinism.
165    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    // WITHOUT ROWID: no rowid column to select or order by. Order by
182    // the single-column primary key (which names the rows) when
183    // present, else by every column so the ordinal keys are stable.
184    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
207/// The rows of a table whose column — `col`, or the row key (the
208/// primary key, else `rowid`) — holds one of `values`, in rowid
209/// order: what a hop reads instead of the table. Each value is
210/// bound in its own storage class and, where it reads as a number
211/// or is one, in the other too, so the comparison finds what the
212/// engine's text equality would under any column affinity; the
213/// statement can only return more, never less, and the model
214/// matches by text afterwards. A WITHOUT ROWID table is declined
215/// (its rows carry positional ids, which a keyed read cannot
216/// assign).
217fn 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
289/// `ANALYZE`'s figure for one value of `column`: the average rows
290/// per distinct key of an index that opens with the column
291/// (`sqlite_stat1.stat`'s second number). An error when no index
292/// opens with it or the statistics are missing.
293fn 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    /// Open a database file (read-only): the catalog now, each
331    /// table's rows on first touch (the connection stays owned by
332    /// the adapter).
333    pub fn open(path: &std::path::Path) -> Result<Self, SqliteError> {
334        Self::open_impl(path, None, &[])
335    }
336
337    /// [`open`], with a declared-references document: each `(field,
338    /// container)` pair supplies the edge the schema does not
339    /// declare (see [`quarb_relational::parse_refs`]).
340    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    /// [`open`], with one table's fetch filtered by a WHERE clause
348    /// (partial pushdown; the engine re-applies the predicates).
349    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    /// [`open_filtered`], with a declared-references document.
358    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        // A hop reads the rows it lands on, by key.
385        .with_key_fetcher(Box::new(move |_, spec, col, values| {
386            fetch_rows_keyed(&keyed, spec, None, col, values)
387        }))
388        // `ANALYZE`'s statistics, when the file carries them: the
389        // table's row count as the planner knows it. A table with
390        // no index has one row with a null `idx`; an indexed table
391        // has one row per index, each opening with the table's row
392        // count. Without statistics the estimate is unknown — a
393        // count would be the scan.
394        .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            // An equality on a column some index opens with: the
409            // index's statistics say how many rows one value of it
410            // selects on average, the planner's own figure. The
411            // smallest over the equalities bounds the read.
412            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        // The pushed-down WHERE filters the table's listing only:
432        // hops into it go by key, unfiltered, so they stay exact.
433        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    /// Open a database held entirely in memory — the bytes of a
445    /// `.db` file that never touched a filesystem (the browser's
446    /// uploaded files). Deserializes read-only, then materializes
447    /// eagerly, as [`SqliteAdapter::load`] does.
448    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    /// Materialize every user table of an open connection, eagerly
455    /// (in-memory databases, tests).
456    pub fn load(conn: &Connection) -> Result<Self, SqliteError> {
457        Self::load_with_refs(conn, &[])
458    }
459
460    /// [`load`], with a declared-references document.
461    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    /// A human-readable locator: `/table/key` for rows.
476    /// Surface a filtered fetch's error now (see
477    /// `RelationalModel::prefetch`): the partial-pushdown caller
478    /// falls back to the scan instead of answering empty.
479    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
488/// Execute pushed-down SQL directly (read-only): the column names
489/// and rows, ordered by `order_table`'s key when one is given (the
490/// pushdown contract: row order must match the adapter's document
491/// order, which is that key).
492/// Whether `cols` covers the primary key or a non-partial UNIQUE
493/// index of `table` — the soundness condition for executing a
494/// witness-JOIN pushdown (see `raw_query`).
495fn 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    // Primary key (pk ordinal > 0 in table_info).
502    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    // Non-partial UNIQUE indexes.
516    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
541/// The catalog for a pushed plan's column-type assumptions
542/// (`quarb_sql::ColumnTypes::hold`): every column of `tables`, with
543/// whether SQLite gives it a numeric affinity. A column of INTEGER,
544/// REAL or NUMERIC affinity holds numbers and compares with a
545/// numeric literal as Quarb's scan does; a TEXT or untyped column
546/// compares by storage class (`'150'` is not `150`, and any text
547/// is greater than any number), which the scan's comparison by
548/// value is not.
549pub 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
566/// SQLite's affinity rules (its "Datatypes" document, section 3.1),
567/// reduced to the question asked: INTEGER, REAL or NUMERIC.
568fn 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    // A witness-JOIN plan is only sound when the ON binds the left
587    // table by a unique key (else SQL multiplies rows where
588    // Quarb's existential binding does not). Verify against the
589    // catalog; refusing sends the caller back to the scan.
590    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    // --explain prints what ran: the ORDER BY key is a
610    // catalog lookup made here, so the final statement
611    // exists nowhere earlier.
612    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    // Every trait method, through the relational model: the search
630    // fast paths, the plan and its estimate included.
631    quarb::forward_ast_adapter!(model);
632}