rust_queries_core/
lock_view.rs

1//! View-like functionality for locked data.
2//!
3//! This module provides saved query patterns (views) that can be reused,
4//! similar to SQL VIEWs.
5//!
6//! # Example
7//!
8//! ```ignore
9//! use rust_queries_core::lock_view::LockView;
10//!
11//! // Define a reusable view
12//! let active_electronics = LockView::new(|map: &ProductMap| {
13//!     map.lock_query()
14//!         .where_(Product::active(), |&a| a)
15//!         .where_(Product::category(), |cat| cat == "Electronics")
16//! });
17//!
18//! // Use the view multiple times
19//! let count = active_electronics.query(&products).count();
20//! let items = active_electronics.query(&products).all();
21//! ```
22
23use crate::lock_query::LockQuery;
24use crate::locks::LockValue;
25use std::marker::PhantomData;
26
27/// A reusable query pattern (like a SQL VIEW).
28///
29/// Views encapsulate query logic that can be reused across multiple queries.
30pub struct LockView<'a, T: 'static, L, F>
31where
32    L: LockValue<T> + 'a,
33    F: Fn(&LockQuery<'a, T, L>) -> LockQuery<'a, T, L>,
34{
35    builder: F,
36    _phantom: PhantomData<(&'a T, L)>,
37}
38
39impl<'a, T: 'static, L, F> LockView<'a, T, L, F>
40where
41    L: LockValue<T> + 'a,
42    F: Fn(&LockQuery<'a, T, L>) -> LockQuery<'a, T, L>,
43{
44    /// Create a new view with a query builder function.
45    ///
46    /// # Example
47    ///
48    /// ```ignore
49    /// let expensive_view = LockView::new(|query| {
50    ///     query.where_(Product::price(), |&p| p > 500.0)
51    /// });
52    /// ```
53    pub fn new(builder: F) -> Self {
54        Self {
55            builder,
56            _phantom: PhantomData,
57        }
58    }
59
60    /// Apply the view to a base query.
61    pub fn apply(&self, base: &LockQuery<'a, T, L>) -> LockQuery<'a, T, L>
62    where
63        T: Clone,
64        L: Clone,
65    {
66        (self.builder)(base)
67    }
68}
69
70/// Materialized view - a cached query result.
71///
72/// Like SQL materialized views, stores query results for fast access.
73pub struct MaterializedLockView<T>
74where
75    T: Clone,
76{
77    data: Vec<T>,
78    refresh_fn: Box<dyn Fn() -> Vec<T>>,
79}
80
81impl<T> MaterializedLockView<T>
82where
83    T: Clone,
84{
85    /// Create a new materialized view with a refresh function.
86    ///
87    /// # Example
88    ///
89    /// ```ignore
90    /// let mat_view = MaterializedLockView::new(|| {
91    ///     product_map
92    ///         .lock_query()
93    ///         .where_(Product::active(), |&a| a)
94    ///         .all()
95    /// });
96    /// ```
97    pub fn new<F>(refresh_fn: F) -> Self
98    where
99        F: Fn() -> Vec<T> + 'static,
100    {
101        let data = refresh_fn();
102        Self {
103            data,
104            refresh_fn: Box::new(refresh_fn),
105        }
106    }
107
108    /// Get the cached data.
109    pub fn get(&self) -> &[T] {
110        &self.data
111    }
112
113    /// Refresh the view with latest data.
114    pub fn refresh(&mut self) {
115        self.data = (self.refresh_fn)();
116    }
117
118    /// Get count without refreshing.
119    pub fn count(&self) -> usize {
120        self.data.len()
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127    use std::sync::{Arc, RwLock};
128    use std::collections::HashMap;
129    use key_paths_derive::Keypath;
130
131    #[derive(Clone, Keypath)]
132    struct Product {
133        id: u32,
134        name: String,
135        price: f64,
136        active: bool,
137    }
138
139    #[test]
140    fn test_materialized_view() {
141        let mut map = HashMap::new();
142        map.insert("p1".to_string(), Arc::new(RwLock::new(Product {
143            id: 1,
144            name: "A".to_string(),
145            price: 100.0,
146            active: true,
147        })));
148
149        let mat_view = MaterializedLockView::new(|| {
150            vec![Product {
151                id: 1,
152                name: "A".to_string(),
153                price: 100.0,
154                active: true,
155            }]
156        });
157
158        assert_eq!(mat_view.count(), 1);
159        assert_eq!(mat_view.get()[0].name, "A");
160    }
161}
162