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