query_flow/
key.rs

1//! Key types for query caching.
2
3use std::any::{Any, TypeId};
4use std::fmt::Debug;
5use std::hash::{Hash, Hasher};
6use std::sync::Arc;
7
8use dyn_hash::DynHash;
9
10/// Object-safe equality comparison.
11///
12/// This trait enables comparing two trait objects for equality
13/// by downcasting and comparing the concrete types.
14pub trait DynEq: Any {
15    /// Compare self with another value for equality.
16    ///
17    /// Returns `true` if `other` is the same concrete type and equal to `self`.
18    fn dyn_eq(&self, other: &dyn Any) -> bool;
19}
20
21impl<T: Eq + 'static> DynEq for T {
22    fn dyn_eq(&self, other: &dyn Any) -> bool {
23        other.downcast_ref::<T>().is_some_and(|o| self == o)
24    }
25}
26
27/// Trait for types that can serve as cache keys.
28///
29/// This trait combines object-safe hashing, equality, and debug formatting.
30/// It is automatically implemented for all types that implement
31/// `Hash + Eq + Debug + Send + Sync + 'static`.
32///
33/// # Object Safety
34///
35/// This trait is object-safe, allowing `Arc<dyn CacheKey>` to be used
36/// in hash maps and other collections.
37pub trait CacheKey: DynHash + DynEq + Debug + Send + Sync {
38    /// Get the key as `Any` for downcasting.
39    fn as_any(&self) -> &dyn Any;
40
41    /// Get the type name for this key.
42    fn type_name(&self) -> &'static str;
43}
44
45impl<T: Hash + Eq + Debug + Send + Sync + 'static> CacheKey for T {
46    fn as_any(&self) -> &dyn Any {
47        self
48    }
49
50    fn type_name(&self) -> &'static str {
51        std::any::type_name::<T>()
52    }
53}
54
55// Enable Hash for dyn CacheKey using the dyn-hash crate
56dyn_hash::hash_trait_object!(CacheKey);
57
58/// Cache key for a query.
59///
60/// Stores the query type and the query value as a type-erased `Arc<dyn CacheKey>`.
61#[derive(Clone)]
62pub struct QueryCacheKey {
63    query_type: TypeId,
64    key: Arc<dyn CacheKey>,
65}
66
67impl QueryCacheKey {
68    /// Create a new query cache key.
69    pub fn new<Q: CacheKey + 'static>(query: Q) -> Self {
70        Self {
71            query_type: TypeId::of::<Q>(),
72            key: Arc::new(query),
73        }
74    }
75
76    /// Get the debug representation of this key.
77    pub fn debug_repr(&self) -> String {
78        format!("{:?}", self.key)
79    }
80
81    /// Downcast the key to its original type.
82    pub fn downcast<K: 'static>(&self) -> Option<&K> {
83        self.key.as_any().downcast_ref()
84    }
85
86    /// Get the query type ID.
87    pub fn query_type(&self) -> TypeId {
88        self.query_type
89    }
90
91    /// Get a reference to the type-erased key.
92    pub fn key(&self) -> &Arc<dyn CacheKey> {
93        &self.key
94    }
95
96    /// Get the type name for this query key.
97    pub fn type_name(&self) -> &'static str {
98        self.key.type_name()
99    }
100}
101
102impl Debug for QueryCacheKey {
103    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104        write!(f, "{:?}", self.key)
105    }
106}
107
108impl Hash for QueryCacheKey {
109    fn hash<H: Hasher>(&self, state: &mut H) {
110        self.query_type.hash(state);
111        self.key.hash(state);
112    }
113}
114
115impl PartialEq for QueryCacheKey {
116    fn eq(&self, other: &Self) -> bool {
117        self.query_type == other.query_type && self.key.dyn_eq(other.key.as_any())
118    }
119}
120
121impl Eq for QueryCacheKey {}
122
123/// Cache key for an asset.
124///
125/// Stores the asset key type and the key value as a type-erased `Arc<dyn CacheKey>`.
126#[derive(Clone)]
127pub struct AssetCacheKey {
128    asset_key_type: TypeId,
129    key: Arc<dyn CacheKey>,
130}
131
132impl AssetCacheKey {
133    /// Create a new asset cache key.
134    pub fn new<K: CacheKey + 'static>(key: K) -> Self {
135        Self {
136            asset_key_type: TypeId::of::<K>(),
137            key: Arc::new(key),
138        }
139    }
140
141    /// Get the debug representation of this key.
142    pub fn debug_repr(&self) -> String {
143        format!("{:?}", self.key)
144    }
145
146    /// Downcast the key to its original type.
147    pub fn downcast<K: 'static>(&self) -> Option<&K> {
148        self.key.as_any().downcast_ref()
149    }
150
151    /// Get the asset key type ID.
152    pub fn asset_key_type(&self) -> TypeId {
153        self.asset_key_type
154    }
155
156    /// Get a reference to the type-erased key.
157    pub fn key(&self) -> &Arc<dyn CacheKey> {
158        &self.key
159    }
160
161    /// Get the type name for this asset key.
162    pub fn type_name(&self) -> &'static str {
163        self.key.type_name()
164    }
165}
166
167impl Debug for AssetCacheKey {
168    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169        write!(f, "Asset({:?})", self.key)
170    }
171}
172
173impl Hash for AssetCacheKey {
174    fn hash<H: Hasher>(&self, state: &mut H) {
175        self.asset_key_type.hash(state);
176        self.key.hash(state);
177    }
178}
179
180impl PartialEq for AssetCacheKey {
181    fn eq(&self, other: &Self) -> bool {
182        self.asset_key_type == other.asset_key_type && self.key.dyn_eq(other.key.as_any())
183    }
184}
185
186impl Eq for AssetCacheKey {}
187
188/// Sentinel for tracking all queries of a type.
189///
190/// Used by `list_queries` to track dependencies on the set of queries,
191/// rather than individual query values.
192#[derive(Clone, Copy)]
193pub struct QuerySetSentinelKey {
194    query_type: TypeId,
195    type_name: &'static str,
196}
197
198impl QuerySetSentinelKey {
199    /// Create a new query set sentinel key.
200    pub fn new<Q: 'static>() -> Self {
201        Self {
202            query_type: TypeId::of::<Q>(),
203            type_name: std::any::type_name::<Q>(),
204        }
205    }
206
207    /// Get the query type ID.
208    pub fn query_type(&self) -> TypeId {
209        self.query_type
210    }
211
212    /// Get the type name for this query set sentinel.
213    pub fn type_name(&self) -> &'static str {
214        self.type_name
215    }
216}
217
218impl Debug for QuerySetSentinelKey {
219    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
220        write!(f, "QuerySet({:?})", self.query_type)
221    }
222}
223
224impl Hash for QuerySetSentinelKey {
225    fn hash<H: Hasher>(&self, state: &mut H) {
226        self.query_type.hash(state);
227    }
228}
229
230impl PartialEq for QuerySetSentinelKey {
231    fn eq(&self, other: &Self) -> bool {
232        self.query_type == other.query_type
233    }
234}
235
236impl Eq for QuerySetSentinelKey {}
237
238/// Sentinel for tracking all asset keys of a type.
239///
240/// Used by `list_asset_keys` to track dependencies on the set of asset keys,
241/// rather than individual asset values.
242#[derive(Clone, Copy)]
243pub struct AssetKeySetSentinelKey {
244    asset_key_type: TypeId,
245    type_name: &'static str,
246}
247
248impl AssetKeySetSentinelKey {
249    /// Create a new asset key set sentinel key.
250    pub fn new<K: 'static>() -> Self {
251        Self {
252            asset_key_type: TypeId::of::<K>(),
253            type_name: std::any::type_name::<K>(),
254        }
255    }
256
257    /// Get the asset key type ID.
258    pub fn asset_key_type(&self) -> TypeId {
259        self.asset_key_type
260    }
261
262    /// Get the type name for this asset key set sentinel.
263    pub fn type_name(&self) -> &'static str {
264        self.type_name
265    }
266}
267
268impl Debug for AssetKeySetSentinelKey {
269    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
270        write!(f, "AssetKeySet({:?})", self.asset_key_type)
271    }
272}
273
274impl Hash for AssetKeySetSentinelKey {
275    fn hash<H: Hasher>(&self, state: &mut H) {
276        self.asset_key_type.hash(state);
277    }
278}
279
280impl PartialEq for AssetKeySetSentinelKey {
281    fn eq(&self, other: &Self) -> bool {
282        self.asset_key_type == other.asset_key_type
283    }
284}
285
286impl Eq for AssetKeySetSentinelKey {}
287
288/// Unified cache key for whale storage.
289///
290/// Used where all key kinds need to be handled together (whale, generic invalidation).
291/// Each variant wraps a specific key type.
292#[derive(Clone)]
293pub enum FullCacheKey {
294    /// A query key.
295    Query(QueryCacheKey),
296    /// An asset key.
297    Asset(AssetCacheKey),
298    /// Sentinel for query set tracking (used by `list_queries`).
299    QuerySetSentinel(QuerySetSentinelKey),
300    /// Sentinel for asset key set tracking (used by `list_asset_keys`).
301    AssetKeySetSentinel(AssetKeySetSentinelKey),
302}
303
304impl FullCacheKey {
305    /// Get the debug representation of this key.
306    pub fn debug_repr(&self) -> String {
307        match self {
308            FullCacheKey::Query(k) => k.debug_repr(),
309            FullCacheKey::Asset(k) => k.debug_repr(),
310            FullCacheKey::QuerySetSentinel(k) => format!("{:?}", k),
311            FullCacheKey::AssetKeySetSentinel(k) => format!("{:?}", k),
312        }
313    }
314
315    /// Downcast the key to its original type.
316    ///
317    /// Returns `None` if the key is not of type `K`.
318    pub fn downcast<K: 'static>(&self) -> Option<&K> {
319        match self {
320            FullCacheKey::Query(k) => k.downcast(),
321            FullCacheKey::Asset(k) => k.downcast(),
322            FullCacheKey::QuerySetSentinel(_) | FullCacheKey::AssetKeySetSentinel(_) => None,
323        }
324    }
325
326    /// Get a reference to the type-erased key (for Query and Asset variants).
327    pub fn key(&self) -> Option<&Arc<dyn CacheKey>> {
328        match self {
329            FullCacheKey::Query(k) => Some(k.key()),
330            FullCacheKey::Asset(k) => Some(k.key()),
331            FullCacheKey::QuerySetSentinel(_) | FullCacheKey::AssetKeySetSentinel(_) => None,
332        }
333    }
334
335    /// Get the type name for this key.
336    pub fn type_name(&self) -> &'static str {
337        match self {
338            FullCacheKey::Query(k) => k.type_name(),
339            FullCacheKey::Asset(k) => k.type_name(),
340            FullCacheKey::QuerySetSentinel(k) => k.type_name(),
341            FullCacheKey::AssetKeySetSentinel(k) => k.type_name(),
342        }
343    }
344}
345
346impl Debug for FullCacheKey {
347    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
348        match self {
349            FullCacheKey::Query(k) => write!(f, "{:?}", k),
350            FullCacheKey::Asset(k) => write!(f, "{:?}", k),
351            FullCacheKey::QuerySetSentinel(k) => write!(f, "{:?}", k),
352            FullCacheKey::AssetKeySetSentinel(k) => write!(f, "{:?}", k),
353        }
354    }
355}
356
357impl Hash for FullCacheKey {
358    fn hash<H: Hasher>(&self, state: &mut H) {
359        std::mem::discriminant(self).hash(state);
360        match self {
361            FullCacheKey::Query(k) => k.hash(state),
362            FullCacheKey::Asset(k) => k.hash(state),
363            FullCacheKey::QuerySetSentinel(k) => k.hash(state),
364            FullCacheKey::AssetKeySetSentinel(k) => k.hash(state),
365        }
366    }
367}
368
369impl PartialEq for FullCacheKey {
370    fn eq(&self, other: &Self) -> bool {
371        match (self, other) {
372            (FullCacheKey::Query(a), FullCacheKey::Query(b)) => a == b,
373            (FullCacheKey::Asset(a), FullCacheKey::Asset(b)) => a == b,
374            (FullCacheKey::QuerySetSentinel(a), FullCacheKey::QuerySetSentinel(b)) => a == b,
375            (FullCacheKey::AssetKeySetSentinel(a), FullCacheKey::AssetKeySetSentinel(b)) => a == b,
376            _ => false,
377        }
378    }
379}
380
381impl Eq for FullCacheKey {}
382
383impl From<QueryCacheKey> for FullCacheKey {
384    fn from(key: QueryCacheKey) -> Self {
385        FullCacheKey::Query(key)
386    }
387}
388
389impl From<AssetCacheKey> for FullCacheKey {
390    fn from(key: AssetCacheKey) -> Self {
391        FullCacheKey::Asset(key)
392    }
393}
394
395impl From<QuerySetSentinelKey> for FullCacheKey {
396    fn from(key: QuerySetSentinelKey) -> Self {
397        FullCacheKey::QuerySetSentinel(key)
398    }
399}
400
401impl From<AssetKeySetSentinelKey> for FullCacheKey {
402    fn from(key: AssetKeySetSentinelKey) -> Self {
403        FullCacheKey::AssetKeySetSentinel(key)
404    }
405}