query_flow/
query.rs

1//! Query trait definition.
2
3use std::sync::Arc;
4
5use crate::db::Db;
6use crate::key::Key;
7use crate::QueryError;
8
9/// A query that can be executed and cached.
10///
11/// Queries are the fundamental unit of computation in query-flow. Each query:
12/// - Has a cache key that uniquely identifies the computation
13/// - Produces an output value
14/// - Can depend on other queries via `db.query()`
15///
16/// # Sync by Design
17///
18/// The `query` method is intentionally synchronous. This avoids the "function
19/// coloring" problem where async infects the entire call stack. For async
20/// operations, use the suspense pattern with `AssetLoadingState`.
21///
22/// # Error Handling
23///
24/// The `query` method returns `Result<Output, QueryError>` where:
25/// - `QueryError` represents system errors (Suspend, Cycle, Cancelled)
26/// - User domain errors should be wrapped in `Output`, e.g., `type Output = Result<T, MyError>`
27///
28/// This means fallible queries return `Ok(Ok(value))` on success and `Ok(Err(error))` on user error.
29///
30/// # Example
31///
32/// ```ignore
33/// use query_flow::{Query, Db, QueryError, Key};
34///
35/// // Simple infallible query
36/// struct Add { a: i32, b: i32 }
37///
38/// impl Query for Add {
39///     type CacheKey = (i32, i32);
40///     type Output = i32;
41///
42///     fn cache_key(&self) -> Self::CacheKey {
43///         (self.a, self.b)
44///     }
45///
46///     fn query(self, _db: &impl Db) -> Result<Arc<Self::Output>, QueryError> {
47///         Ok(Arc::new(self.a + self.b))
48///     }
49/// }
50///
51/// // Fallible query with user errors
52/// struct ParseInt { input: String }
53///
54/// impl Query for ParseInt {
55///     type CacheKey = String;
56///     type Output = Result<i32, std::num::ParseIntError>;
57///
58///     fn cache_key(&self) -> Self::CacheKey {
59///         self.input.clone()
60///     }
61///
62///     fn query(self, _db: &impl Db) -> Result<Arc<Self::Output>, QueryError> {
63///         Ok(Arc::new(self.input.parse()))  // Ok(Arc(Ok(n))) or Ok(Arc(Err(parse_error)))
64///     }
65/// }
66/// ```
67pub trait Query: Clone + Send + Sync + 'static {
68    /// The cache key type for this query.
69    ///
70    /// Two queries with the same cache key are considered equivalent and
71    /// will share cached results.
72    type CacheKey: Key;
73
74    /// The output type of this query.
75    ///
76    /// For fallible queries, use `Result<T, E>` here.
77    type Output: Send + Sync + 'static;
78
79    /// Get the cache key for this query instance.
80    fn cache_key(&self) -> Self::CacheKey;
81
82    /// Execute the query, returning the output wrapped in Arc or a system error.
83    ///
84    /// The result is wrapped in `Arc` for efficient sharing in the cache.
85    /// Use the `#[query]` macro to automatically handle Arc wrapping.
86    ///
87    /// # Arguments
88    ///
89    /// * `db` - The database for accessing dependencies
90    ///
91    /// # Returns
92    ///
93    /// * `Ok(arc_output)` - Query completed successfully
94    /// * `Err(QueryError::Suspend)` - Query is waiting for async loading
95    /// * `Err(QueryError::Cycle)` - Dependency cycle detected
96    fn query(self, db: &impl Db) -> Result<Arc<Self::Output>, QueryError>;
97
98    /// Compare two outputs for equality (for early cutoff optimization).
99    ///
100    /// When a query is recomputed and the output is equal to the previous
101    /// output, downstream queries can skip recomputation (early cutoff).
102    ///
103    /// The `#[query]` macro generates this using `PartialEq` by default.
104    /// Use `output_eq = custom_fn` for types without `PartialEq`.
105    fn output_eq(old: &Self::Output, new: &Self::Output) -> bool;
106}