query_flow/query.rs
1//! Query trait definition.
2
3use std::sync::Arc;
4
5use crate::db::Db;
6use crate::key::CacheKey;
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/// - Is itself the cache key (implements `Hash + Eq`)
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/// ```
33/// use std::sync::Arc;
34///
35/// use query_flow::{Query, Db, QueryError};
36///
37/// // Simple infallible query
38/// #[derive(Clone, Debug, Hash, PartialEq, Eq)]
39/// struct Add { a: i32, b: i32 }
40///
41/// impl Query for Add {
42/// type Output = i32;
43///
44/// fn query(self, _db: &impl Db) -> Result<Arc<Self::Output>, QueryError> {
45/// Ok(Arc::new(self.a + self.b))
46/// }
47///
48/// fn output_eq(old: &Self::Output, new: &Self::Output) -> bool {
49/// old == new
50/// }
51/// }
52///
53/// // Fallible query with user errors
54/// #[derive(Clone, Debug, Hash, PartialEq, Eq)]
55/// struct ParseInt { input: String }
56///
57/// impl Query for ParseInt {
58/// type Output = Result<i32, std::num::ParseIntError>;
59///
60/// fn query(self, _db: &impl Db) -> Result<Arc<Self::Output>, QueryError> {
61/// Ok(Arc::new(self.input.parse())) // Ok(Arc(Ok(n))) or Ok(Arc(Err(parse_error)))
62/// }
63///
64/// fn output_eq(old: &Self::Output, new: &Self::Output) -> bool {
65/// old == new
66/// }
67/// }
68/// ```
69pub trait Query: CacheKey + Clone + Send + Sync + 'static {
70 /// The output type of this query.
71 ///
72 /// For fallible queries, use `Result<T, E>` here.
73 type Output: Send + Sync + 'static;
74
75 /// Execute the query, returning the output wrapped in Arc or a system error.
76 ///
77 /// The result is wrapped in `Arc` for efficient sharing in the cache.
78 /// Use the `#[query]` macro to automatically handle Arc wrapping.
79 ///
80 /// # Arguments
81 ///
82 /// * `db` - The database for accessing dependencies
83 ///
84 /// # Returns
85 ///
86 /// * `Ok(arc_output)` - Query completed successfully
87 /// * `Err(QueryError::Suspend)` - Query is waiting for async loading
88 /// * `Err(QueryError::Cycle)` - Dependency cycle detected
89 fn query(self, db: &impl Db) -> Result<Arc<Self::Output>, QueryError>;
90
91 /// Compare two outputs for equality (for early cutoff optimization).
92 ///
93 /// When a query is recomputed and the output is equal to the previous
94 /// output, downstream queries can skip recomputation (early cutoff).
95 ///
96 /// The `#[query]` macro generates this using `PartialEq` by default.
97 /// Use `output_eq = custom_fn` for types without `PartialEq`.
98 fn output_eq(old: &Self::Output, new: &Self::Output) -> bool;
99}
100
101/// Convenience trait for query output types.
102///
103/// This trait combines the bounds needed for a type to be used as a query output:
104/// `PartialEq + Send + Sync + 'static`.
105///
106/// - `PartialEq` is required for the default `output_eq` comparison (early cutoff optimization)
107/// - `Send + Sync + 'static` allows the output to be cached and shared across threads
108///
109/// # When to Use
110///
111/// Use `QueryOutput` for generic type parameters that appear only in query output:
112///
113/// ```
114/// use std::fmt::Display;
115/// use std::str::FromStr;
116///
117/// use query_flow::{query, Db, QueryError, QueryOutput, QueryRuntime};
118///
119/// #[query]
120/// fn parse<T: QueryOutput + FromStr>(db: &impl Db, text: String) -> Result<T, QueryError>
121/// where
122/// T::Err: Display,
123/// {
124/// let _ = db;
125/// text.parse().map_err(|e| anyhow::anyhow!("{}", e).into())
126/// }
127///
128/// let runtime = QueryRuntime::new();
129/// assert_eq!(*runtime.query(Parse::<i32>::new("42".into())).unwrap(), 42);
130/// assert_eq!(*runtime.query(Parse::<u64>::new("42".into())).unwrap(), 42u64);
131/// ```
132///
133/// # When Not to Use
134///
135/// If you supply a custom comparator with `#[query(output_eq = path)]`, the output
136/// does not need `PartialEq`. In that case, use raw bounds instead:
137///
138/// ```
139/// use query_flow::{query, Db, QueryError, QueryRuntime};
140///
141/// // No `PartialEq` on `T`, so the comparator is supplied explicitly.
142/// // Returning `false` disables early cutoff: dependents always recompute.
143/// fn always_recompute<T>(_old: &T, _new: &T) -> bool {
144/// false
145/// }
146///
147/// #[query(output_eq = always_recompute)]
148/// fn create<T: Default + Send + Sync + 'static>(db: &impl Db) -> Result<T, QueryError> {
149/// let _ = db;
150/// Ok(T::default())
151/// }
152///
153/// let runtime = QueryRuntime::new();
154/// assert_eq!(*runtime.query(Create::<i32>::new()).unwrap(), 0);
155/// ```
156pub trait QueryOutput: PartialEq + Send + Sync + 'static {}
157impl<T: PartialEq + Send + Sync + 'static> QueryOutput for T {}