Skip to main content

mentat_transaction/
query.rs

1// Copyright 2016-2018 Mozilla
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
4// this file except in compliance with the License. You may obtain a copy of the
5// License at http://www.apache.org/licenses/LICENSE-2.0
6// Unless required by applicable law or agreed to in writing, software distributed
7// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
8// CONDITIONS OF ANY KIND, either express or implied. See the License for the
9// specific language governing permissions and limitations under the License.
10
11use rusqlite;
12use rusqlite::params_from_iter;
13use rusqlite::types::ToSql;
14
15use std::rc::Rc;
16
17use core_traits::{Binding, Entid, KnownEntid, TypedValue};
18
19use mentat_core::{HasSchema, Schema};
20
21use mentat_query_algebrizer::{
22    algebrize_with_inputs, algebrize_with_inputs_and_temporal, parse_find_string, AlgebraicQuery,
23    EmptyBecause, FindQuery, TemporalBound,
24};
25
26pub use mentat_query_algebrizer::QueryInputs;
27
28pub use edn::query::{Keyword, PlainSymbol, Variable};
29
30use edn::query::{
31    Element, FindSpec, Pattern, PatternNonValuePlace, PatternValuePlace, WhereClause,
32};
33
34use mentat_query_projector::{ConstantProjector, Projector};
35
36use mentat_query_projector::translate::{query_to_select, ProjectedSelect};
37
38use mentat_sql::{SQLQuery, SqlConn, SqlValue};
39
40use mentat_query_projector::{RusqliteRows, VecRows};
41
42pub use mentat_query_algebrizer::Known;
43
44pub use mentat_query_projector::{
45    QueryOutput,  // Includes the columns/find spec.
46    QueryResults, // The results themselves.
47    RelResult,
48};
49
50use public_traits::errors::{MentatError, Result};
51
52pub type QueryExecutionResult = Result<QueryOutput>;
53pub type PreparedResult<'sqlite> = Result<PreparedQuery<'sqlite>>;
54
55pub enum PreparedQuery<'sqlite> {
56    Empty {
57        find_spec: Rc<FindSpec>,
58    },
59    Constant {
60        select: ConstantProjector,
61    },
62    Bound {
63        statement: rusqlite::Statement<'sqlite>,
64        schema: Schema,
65        connection: &'sqlite rusqlite::Connection,
66        args: Vec<(String, Rc<rusqlite::types::Value>)>,
67        projector: Box<dyn Projector>,
68    },
69}
70
71impl<'sqlite> PreparedQuery<'sqlite> {
72    pub fn run<T>(&mut self, _inputs: T) -> QueryExecutionResult
73    where
74        T: Into<Option<QueryInputs>>,
75    {
76        match self {
77            PreparedQuery::Empty { ref find_spec } => Ok(QueryOutput::empty(find_spec)),
78            PreparedQuery::Constant { ref select } => {
79                select.project_without_rows().map_err(|e| e.into())
80            }
81            PreparedQuery::Bound {
82                ref mut statement,
83                ref schema,
84                connection,
85                ref args,
86                ref projector,
87            } => {
88                let mut rows = RusqliteRows::new(run_statement(statement, args)?);
89                projector
90                    .project(schema, &mentat_sql::sqlite(connection), &mut rows)
91                    .map_err(|e| e.into())
92            }
93        }
94    }
95}
96
97pub trait IntoResult {
98    fn into_scalar_result(self) -> Result<Option<Binding>>;
99    fn into_coll_result(self) -> Result<Vec<Binding>>;
100    fn into_tuple_result(self) -> Result<Option<Vec<Binding>>>;
101    fn into_rel_result(self) -> Result<RelResult<Binding>>;
102}
103
104impl IntoResult for QueryExecutionResult {
105    fn into_scalar_result(self) -> Result<Option<Binding>> {
106        self?.into_scalar().map_err(|e| e.into())
107    }
108
109    fn into_coll_result(self) -> Result<Vec<Binding>> {
110        self?.into_coll().map_err(|e| e.into())
111    }
112
113    fn into_tuple_result(self) -> Result<Option<Vec<Binding>>> {
114        self?.into_tuple().map_err(|e| e.into())
115    }
116
117    fn into_rel_result(self) -> Result<RelResult<Binding>> {
118        self?.into_rel().map_err(|e| e.into())
119    }
120}
121
122/// A struct describing information about how Mentat would execute a query.
123pub enum QueryExplanation {
124    /// A query known in advance to be empty, and why we believe that.
125    KnownEmpty(EmptyBecause),
126
127    /// A query known in advance to return a constant value.
128    KnownConstant,
129
130    /// A query that takes actual work to execute.
131    ExecutionPlan {
132        /// The translated query and any bindings.
133        query: SQLQuery,
134        /// The output of SQLite's `EXPLAIN QUERY PLAN`.
135        steps: Vec<QueryPlanStep>,
136    },
137}
138
139/// A single row in the output of SQLite's `EXPLAIN QUERY PLAN`.
140/// See https://www.sqlite.org/eqp.html for an explanation of each field.
141pub struct QueryPlanStep {
142    pub select_id: i32,
143    pub order: i32,
144    pub from: i32,
145    pub detail: String,
146}
147
148fn algebrize_query<T>(known: Known, query: FindQuery, inputs: T) -> Result<AlgebraicQuery>
149where
150    T: Into<Option<QueryInputs>>,
151{
152    let algebrized = algebrize_with_inputs(known, query, 0, inputs.into().unwrap_or_default())?;
153    let unbound = algebrized.unbound_variables();
154    // Because we are running once, we can check that all of our `:in` variables are bound at this point.
155    // If they aren't, the user has made an error -- perhaps writing the wrong variable in `:in`, or
156    // not binding in the `QueryInput`.
157    if !unbound.is_empty() {
158        bail!(MentatError::UnboundVariables(
159            unbound.into_iter().map(|v| v.to_string()).collect()
160        ));
161    }
162    Ok(algebrized)
163}
164
165fn fetch_values(
166    sqlite: &rusqlite::Connection,
167    known: Known,
168    entity: Entid,
169    attribute: Entid,
170    only_one: bool,
171) -> QueryExecutionResult {
172    let v = Variable::from_valid_name("?v");
173
174    // This should never fail.
175    // TODO: it should be possible to algebrize with variable entity and attribute,
176    // particularly with known type, allowing the use of prepared statements.
177    let pattern = Pattern::simple(
178        PatternNonValuePlace::Entid(entity),
179        PatternNonValuePlace::Entid(attribute),
180        PatternValuePlace::Variable(v.clone()),
181    )
182    .unwrap();
183
184    let element = Element::Variable(v);
185    let spec = if only_one {
186        FindSpec::FindScalar(element)
187    } else {
188        FindSpec::FindColl(element)
189    };
190    let query = FindQuery::simple(spec, vec![WhereClause::Pattern(pattern)]);
191
192    let algebrized = algebrize_query(known, query, None)?;
193
194    run_algebrized_query(known, sqlite, algebrized)
195}
196
197fn lookup_attribute(schema: &Schema, attribute: &Keyword) -> Result<KnownEntid> {
198    schema
199        .get_entid(attribute)
200        .ok_or_else(|| MentatError::UnknownAttribute(attribute.name().into()))
201}
202
203/// Return a single value for the provided entity and attribute.
204/// If the attribute is multi-valued, an arbitrary value is returned.
205/// If no value is present for that entity, `None` is returned.
206/// If `attribute` isn't an attribute, `None` is returned.
207#[allow(clippy::extra_unused_lifetimes)]
208pub fn lookup_value<'schema, 'cache, E, A>(
209    sqlite: &rusqlite::Connection,
210    known: Known,
211    entity: E,
212    attribute: A,
213) -> Result<Option<TypedValue>>
214where
215    E: Into<Entid>,
216    A: Into<Entid>,
217{
218    let entid = entity.into();
219    let attrid = attribute.into();
220
221    if known.is_attribute_cached_forward(attrid) {
222        Ok(known
223            .get_value_for_entid(known.schema, attrid, entid)
224            .cloned())
225    } else {
226        fetch_values(sqlite, known, entid, attrid, true)
227            .into_scalar_result()
228            // Safe to unwrap: we never retrieve structure.
229            .map(|r| r.map(|v| v.into_scalar().unwrap()))
230    }
231}
232
233pub fn lookup_values<E, A>(
234    sqlite: &rusqlite::Connection,
235    known: Known,
236    entity: E,
237    attribute: A,
238) -> Result<Vec<TypedValue>>
239where
240    E: Into<Entid>,
241    A: Into<Entid>,
242{
243    let entid = entity.into();
244    let attrid = attribute.into();
245
246    if known.is_attribute_cached_forward(attrid) {
247        Ok(known
248            .get_values_for_entid(known.schema, attrid, entid)
249            .cloned()
250            .unwrap_or_else(Vec::new))
251    } else {
252        fetch_values(sqlite, known, entid, attrid, false)
253            .into_coll_result()
254            // Safe to unwrap: we never retrieve structure.
255            .map(|v| v.into_iter().map(|x| x.into_scalar().unwrap()).collect())
256    }
257}
258
259/// Return a single value for the provided entity and attribute.
260/// If the attribute is multi-valued, an arbitrary value is returned.
261/// If no value is present for that entity, `None` is returned.
262/// If `attribute` doesn't name an attribute, an error is returned.
263pub fn lookup_value_for_attribute<E>(
264    sqlite: &rusqlite::Connection,
265    known: Known,
266    entity: E,
267    attribute: &Keyword,
268) -> Result<Option<TypedValue>>
269where
270    E: Into<Entid>,
271{
272    let attribute = lookup_attribute(known.schema, attribute)?;
273    lookup_value(sqlite, known, entity.into(), attribute)
274}
275
276pub fn lookup_values_for_attribute<E>(
277    sqlite: &rusqlite::Connection,
278    known: Known,
279    entity: E,
280    attribute: &Keyword,
281) -> Result<Vec<TypedValue>>
282where
283    E: Into<Entid>,
284{
285    let attribute = lookup_attribute(known.schema, attribute)?;
286    lookup_values(sqlite, known, entity.into(), attribute)
287}
288
289fn run_statement<'sqlite, 'stmt, 'bound>(
290    statement: &'stmt mut rusqlite::Statement<'sqlite>,
291    bindings: &'bound [(String, Rc<rusqlite::types::Value>)],
292) -> Result<rusqlite::Rows<'stmt>> {
293    let rows = if bindings.is_empty() {
294        statement.query(rusqlite::params![])?
295    } else {
296        let params: Vec<&dyn ToSql> = bindings
297            .iter()
298            .map(|(_, v)| v.as_ref() as &dyn ToSql)
299            .collect();
300        statement.query(params_from_iter(params.iter()))?
301    };
302    Ok(rows)
303}
304
305fn run_sql_query<T, F>(
306    sqlite: &rusqlite::Connection,
307    sql: &str,
308    bindings: &[(String, Rc<rusqlite::types::Value>)],
309    mut mapper: F,
310) -> Result<Vec<T>>
311where
312    F: FnMut(&rusqlite::Row) -> rusqlite::Result<T>,
313{
314    let mut statement = sqlite.prepare(sql)?;
315    let mut rows = run_statement(&mut statement, bindings)?;
316    let mut result = vec![];
317    while let Some(row) = rows.next()? {
318        result.push(mapper(row)?);
319    }
320    Ok(result)
321}
322
323fn algebrize_query_str<T>(known: Known, query: &str, inputs: T) -> Result<AlgebraicQuery>
324where
325    T: Into<Option<QueryInputs>>,
326{
327    let parsed = parse_find_string(query)?;
328    algebrize_query(known, parsed, inputs)
329}
330
331fn run_algebrized_query(
332    known: Known,
333    sqlite: &rusqlite::Connection,
334    algebrized: AlgebraicQuery,
335) -> QueryExecutionResult {
336    assert!(
337        algebrized.unbound_variables().is_empty(),
338        "Unbound variables should be checked by now"
339    );
340    if algebrized.is_known_empty() {
341        // We don't need to do any SQL work at all.
342        return Ok(QueryOutput::empty(&algebrized.find_spec));
343    }
344
345    let select = query_to_select(known.schema, algebrized)?;
346    match select {
347        ProjectedSelect::Constant(constant) => {
348            constant.project_without_rows().map_err(|e| e.into())
349        }
350        ProjectedSelect::Query { query, projector } => {
351            let SQLQuery { sql, args } = query.to_sql_query()?;
352
353            let mut statement = sqlite.prepare(sql.as_str())?;
354            let mut rows = RusqliteRows::new(run_statement(&mut statement, &args)?);
355
356            projector
357                .project(known.schema, &mentat_sql::sqlite(sqlite), &mut rows)
358                .map_err(|e| e.into())
359        }
360    }
361}
362
363/// `run_algebrized_query` for any engine behind the storage seam: the SQL is
364/// built for `conn.dialect()`, run with named parameters, and the rows are
365/// projected exactly as on SQLite. The DuckDB extension queries through this.
366pub fn run_algebrized_query_on(
367    known: Known,
368    conn: &dyn SqlConn,
369    algebrized: AlgebraicQuery,
370) -> QueryExecutionResult {
371    if !algebrized.unbound_variables().is_empty() {
372        bail!(MentatError::UnboundVariables(
373            algebrized
374                .unbound_variables()
375                .into_iter()
376                .map(|v| v.to_string())
377                .collect()
378        ));
379    }
380    if algebrized.is_known_empty() {
381        return Ok(QueryOutput::empty(&algebrized.find_spec));
382    }
383    match query_to_select(known.schema, algebrized)? {
384        ProjectedSelect::Constant(constant) => {
385            constant.project_without_rows().map_err(|e| e.into())
386        }
387        ProjectedSelect::Query { query, projector } => {
388            let SQLQuery { sql, args } = query.to_sql_query_for(conn.dialect())?;
389            let params: Vec<(&str, SqlValue)> = args
390                .iter()
391                .map(|(name, v)| (name.as_str(), mentat_sql::from_rusqlite(v.as_ref().clone())))
392                .collect();
393            let mut rows: Vec<Vec<SqlValue>> = Vec::new();
394            conn.query_named(&sql, &params, &mut |row| {
395                let n = row.column_count();
396                let mut vals = Vec::with_capacity(n);
397                for i in 0..n {
398                    vals.push(row.get_value(i)?);
399                }
400                rows.push(vals);
401                Ok(())
402            })?;
403            projector
404                .project(known.schema, conn, &mut VecRows::new(rows))
405                .map_err(|e| e.into())
406        }
407    }
408}
409
410/// `q_once` for any engine behind the storage seam, with an optional
411/// historical basis (`as-of` / `since`).
412pub fn q_once_on<T>(
413    conn: &dyn SqlConn,
414    known: Known,
415    query: &str,
416    inputs: T,
417    temporal: Option<TemporalBound>,
418) -> QueryExecutionResult
419where
420    T: Into<Option<QueryInputs>>,
421{
422    let parsed = parse_find_string(query)?;
423    let algebrized = algebrize_with_inputs_and_temporal(
424        known,
425        parsed,
426        0,
427        inputs.into().unwrap_or_default(),
428        temporal,
429    )?;
430    run_algebrized_query_on(known, conn, algebrized)
431}
432
433/// Take an EDN query string, a reference to an open SQLite connection, a Mentat schema, and an
434/// optional collection of input bindings (which should be keyed by `"?varname"`), and execute the
435/// query immediately, blocking the current thread.
436/// Returns a structure that corresponds to the kind of input query, populated with `TypedValue`
437/// instances.
438/// The caller is responsible for ensuring that the SQLite connection has an open transaction if
439/// isolation is required.
440pub fn q_once<T>(
441    sqlite: &rusqlite::Connection,
442    known: Known,
443    query: &str,
444    inputs: T,
445) -> QueryExecutionResult
446where
447    T: Into<Option<QueryInputs>>,
448{
449    let algebrized = algebrize_query_str(known, query, inputs)?;
450    run_algebrized_query(known, sqlite, algebrized)
451}
452
453/// `q_once`, also returning the attributes the query filtered by value
454/// (`ConjoiningClauses::value_filtered_attributes`), for index advice.
455pub fn q_once_value_filtered<T>(
456    sqlite: &rusqlite::Connection,
457    known: Known,
458    query: &str,
459    inputs: T,
460) -> Result<(QueryOutput, std::collections::BTreeSet<Entid>)>
461where
462    T: Into<Option<QueryInputs>>,
463{
464    let mut algebrized = algebrize_query_str(known, query, inputs)?;
465    let attrs = std::mem::take(&mut algebrized.cc.value_filtered_attributes);
466    Ok((run_algebrized_query(known, sqlite, algebrized)?, attrs))
467}
468
469/// Like `q_once`, but runs the query against a historical basis: `as-of T`
470/// reconstructs the state at transaction `T`, `since T` sees only datoms
471/// transacted after `T`. This is the SQLite engine's historical-`q` entry point.
472pub fn q_once_temporal<T>(
473    sqlite: &rusqlite::Connection,
474    known: Known,
475    query: &str,
476    inputs: T,
477    temporal: TemporalBound,
478) -> QueryExecutionResult
479where
480    T: Into<Option<QueryInputs>>,
481{
482    let parsed = parse_find_string(query)?;
483    let algebrized = algebrize_with_inputs_and_temporal(
484        known,
485        parsed,
486        0,
487        inputs.into().unwrap_or_default(),
488        Some(temporal),
489    )?;
490    let unbound = algebrized.unbound_variables();
491    if !unbound.is_empty() {
492        bail!(MentatError::UnboundVariables(
493            unbound.into_iter().map(|v| v.to_string()).collect()
494        ));
495    }
496    run_algebrized_query(known, sqlite, algebrized)
497}
498
499/// Just like `q_once`, but doesn't use any cached values.
500pub fn q_uncached<T>(
501    sqlite: &rusqlite::Connection,
502    schema: &Schema,
503    query: &str,
504    inputs: T,
505) -> QueryExecutionResult
506where
507    T: Into<Option<QueryInputs>>,
508{
509    let known = Known::for_schema(schema);
510    let algebrized = algebrize_query_str(known, query, inputs)?;
511
512    run_algebrized_query(known, sqlite, algebrized)
513}
514
515pub fn q_prepare<'sqlite, 'schema, 'cache, 'query, T>(
516    sqlite: &'sqlite rusqlite::Connection,
517    known: Known<'schema, 'cache>,
518    query: &'query str,
519    inputs: T,
520) -> PreparedResult<'sqlite>
521where
522    T: Into<Option<QueryInputs>>,
523{
524    let algebrized = algebrize_query_str(known, query, inputs)?;
525
526    let unbound = algebrized.unbound_variables();
527    if !unbound.is_empty() {
528        // TODO: Allow binding variables at execution time, not just
529        // preparation time.
530        bail!(MentatError::UnboundVariables(
531            unbound.into_iter().map(|v| v.to_string()).collect()
532        ));
533    }
534
535    if algebrized.is_known_empty() {
536        // We don't need to do any SQL work at all.
537        return Ok(PreparedQuery::Empty {
538            find_spec: algebrized.find_spec,
539        });
540    }
541
542    let select = query_to_select(known.schema, algebrized)?;
543    match select {
544        ProjectedSelect::Constant(constant) => Ok(PreparedQuery::Constant { select: constant }),
545        ProjectedSelect::Query { query, projector } => {
546            let SQLQuery { sql, args } = query.to_sql_query()?;
547            let statement = sqlite.prepare(sql.as_str())?;
548
549            Ok(PreparedQuery::Bound {
550                statement,
551                schema: known.schema.clone(),
552                connection: sqlite,
553                args,
554                projector,
555            })
556        }
557    }
558}
559
560pub fn q_explain<T>(
561    sqlite: &rusqlite::Connection,
562    known: Known,
563    query: &str,
564    inputs: T,
565) -> Result<QueryExplanation>
566where
567    T: Into<Option<QueryInputs>>,
568{
569    q_explain_temporal(sqlite, known, query, inputs, None)
570}
571
572/// `q_explain` for a query run against a historical basis (`as-of`/`since`).
573pub fn q_explain_temporal<T>(
574    sqlite: &rusqlite::Connection,
575    known: Known,
576    query: &str,
577    inputs: T,
578    temporal: Option<TemporalBound>,
579) -> Result<QueryExplanation>
580where
581    T: Into<Option<QueryInputs>>,
582{
583    let parsed = parse_find_string(query)?;
584    let algebrized = algebrize_with_inputs_and_temporal(
585        known,
586        parsed,
587        0,
588        inputs.into().unwrap_or_default(),
589        temporal,
590    )?;
591    if algebrized.is_known_empty() {
592        return Ok(QueryExplanation::KnownEmpty(
593            algebrized.cc.empty_because.unwrap(),
594        ));
595    }
596    match query_to_select(known.schema, algebrized)? {
597        ProjectedSelect::Constant(_constant) => Ok(QueryExplanation::KnownConstant),
598        ProjectedSelect::Query {
599            query,
600            projector: _projector,
601        } => {
602            let query = query.to_sql_query()?;
603
604            let plan_sql = format!("EXPLAIN QUERY PLAN {}", query.sql);
605
606            let steps = run_sql_query(sqlite, &plan_sql, &query.args, |row| {
607                Ok(QueryPlanStep {
608                    select_id: row.get(0)?,
609                    order: row.get(1)?,
610                    from: row.get(2)?,
611                    detail: row.get(3)?,
612                })
613            })?;
614
615            Ok(QueryExplanation::ExecutionPlan { query, steps })
616        }
617    }
618}