Skip to main content

query_lang/
system.rs

1//! The trait a consumer implements to define its derived queries.
2
3use crate::database::Database;
4use crate::error::QueryError;
5
6/// The definition of a query system: how every derived query is computed.
7///
8/// A consumer implements `System` once to describe an entire incremental
9/// computation. It ties together three things: the [`Key`](Self::Key) that names
10/// a query, the [`Value`](Self::Value) a query produces, and the
11/// [`compute`](Self::compute) function that turns one into the other. The
12/// [`Database`](crate::Database) supplies everything else — caching, dependency
13/// tracking, and invalidation — and calls `compute` only when it must.
14///
15/// # Keys, inputs, and derived queries
16///
17/// A single `Key` type names every query in the system, usually an `enum` with
18/// one variant per kind of query (`Key::Source(FileId)`, `Key::Ast(FileId)`,
19/// `Key::TypeOf(DefId)`, …). A key is an **input** once its value is placed into
20/// the database with [`Database::set`](crate::Database::set); every other key is
21/// **derived**, and its value comes from `compute`. The same key type covers
22/// both, so a query reads an input and another derived query the same way —
23/// through [`Database::get`](crate::Database::get) — and the engine records the
24/// dependency either way.
25///
26/// # The contract on `compute`
27///
28/// `compute` must be a pure function of the queries it reads. It may read inputs
29/// and other derived queries through the `db` handle it is given, and it must
30/// read *every* value it depends on through that handle — a value pulled in from
31/// outside (a global, the clock, the filesystem read directly) is invisible to
32/// the engine and will not trigger invalidation when it changes, leaving the
33/// cache serving stale results. Given the same inputs, `compute` must return the
34/// same value; the engine relies on that to reuse cached results safely.
35///
36/// # Requirements on the associated types
37///
38/// - `Key: Clone + Ord` — keys are stored in the dependency graph and the memo
39///   table (a `BTreeMap`, so `Ord` rather than `Hash`; this keeps the engine
40///   `no_std`- and dependency-free). Cloning a key should be cheap; prefer small
41///   copyable keys or interned identifiers over owned strings.
42/// - `Value: Clone + Eq` — the engine clones a value to hand it back and compares
43///   the new value against the old one to decide whether a recomputation actually
44///   changed anything. That comparison is *early cutoff*: when a recomputed value
45///   equals its predecessor, queries that depend on it are not recomputed. Make
46///   values cheap to clone and compare — wrap large results in an
47///   [`Arc`](std::sync::Arc) so a clone bumps a refcount rather than copying.
48///
49/// # Examples
50///
51/// A two-layer system: an input number, and a derived query that squares it.
52///
53/// ```
54/// use query_lang::{Database, System, QueryError};
55///
56/// // One enum names every query. `Base` values are set as inputs; `Squared`
57/// // values are computed from a `Base`.
58/// #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
59/// enum Key {
60///     Base,
61///     Squared,
62/// }
63///
64/// struct Arithmetic;
65/// impl System for Arithmetic {
66///     type Key = Key;
67///     type Value = i64;
68///     fn compute(&self, db: &Database<Self>, key: &Key) -> Result<i64, QueryError> {
69///         match key {
70///             Key::Base => Ok(0), // a default if `Base` was never set as an input
71///             Key::Squared => {
72///                 let base = db.get(&Key::Base)?;
73///                 Ok(base * base)
74///             }
75///         }
76///     }
77/// }
78///
79/// let mut db = Database::new(Arithmetic);
80/// db.set(Key::Base, 9);
81/// assert_eq!(db.get(&Key::Squared)?, 81);
82/// # Ok::<(), QueryError>(())
83/// ```
84pub trait System: Sized {
85    /// The identifier that names a query. Usually an `enum` with one variant per
86    /// kind of query. Must be cheap to clone and totally ordered.
87    type Key: Clone + Ord;
88
89    /// The value a query produces. Cloned to return and compared for early
90    /// cutoff, so it should be cheap to clone and compare (wrap large payloads in
91    /// an [`Arc`](std::sync::Arc)).
92    type Value: Clone + Eq;
93
94    /// Compute the value of a derived query.
95    ///
96    /// The engine calls this only on a cache miss or when a dependency has
97    /// genuinely changed — never for a key that is currently a set input, and
98    /// never when a cached value is still valid. Read every dependency through
99    /// `db` so the engine can track it; see the [trait
100    /// contract](Self#the-contract-on-compute).
101    ///
102    /// # Errors
103    ///
104    /// Returns [`QueryError::Cycle`] if resolving a dependency closes a cycle
105    /// back onto a query still being computed. Propagate it with `?`; do not
106    /// attempt to recover from it inside `compute`, as the whole resolution
107    /// chain is already unwinding.
108    fn compute(&self, db: &Database<Self>, key: &Self::Key) -> Result<Self::Value, QueryError>;
109}