Skip to main content

key_paths_iter/
lib.rs

1//! Query builder for collection keypaths over [rust_key_paths::KpType] when the value type is `Vec<Item>`.
2//!
3//! Enable the `rayon` feature for parallel collection operations ([`query_par`]).
4
5use rust_key_paths::KpType;
6
7#[cfg(feature = "rayon")]
8pub mod query_par;
9#[cfg(feature = "rayon")]
10pub mod rayon_optimizations;
11#[cfg(feature = "rayon")]
12pub mod scale_par;
13
14#[cfg(feature = "gpu")]
15pub mod wgpu;
16#[cfg(feature = "gpu")]
17pub mod kp_gpu;
18
19/// Query builder for collection keypaths (KpType where value is `Vec<Item>`).
20pub struct CollectionQuery<'a, Root, Item> {
21    keypath: &'a KpType<'a, Root, Vec<Item>>,
22    filters: Vec<Box<dyn Fn(&Item) -> bool + 'a>>,
23    limit: Option<usize>,
24    offset: usize,
25}
26
27impl<'a, Root, Item> CollectionQuery<'a, Root, Item> {
28    pub fn new(keypath: &'a KpType<'a, Root, Vec<Item>>) -> Self {
29        Self {
30            keypath,
31            filters: Vec::new(),
32            limit: None,
33            offset: 0,
34        }
35    }
36
37    pub fn filter<F>(mut self, predicate: F) -> Self
38    where
39        F: Fn(&Item) -> bool + 'a,
40    {
41        self.filters.push(Box::new(predicate));
42        self
43    }
44
45    pub fn limit(mut self, n: usize) -> Self {
46        self.limit = Some(n);
47        self
48    }
49
50    pub fn offset(mut self, n: usize) -> Self {
51        self.offset = n;
52        self
53    }
54
55    pub fn execute(&self, root: &'a Root) -> Vec<&'a Item> {
56        if let Some(vec) = self.keypath.get(root) {
57            let mut result: Vec<&'a Item> = vec
58                .iter()
59                .skip(self.offset)
60                .filter(|item| self.filters.iter().all(|f| f(item)))
61                .collect();
62
63            if let Some(limit) = self.limit {
64                result.truncate(limit);
65            }
66
67            result
68        } else {
69            Vec::new()
70        }
71    }
72
73    pub fn count(&self, root: &'a Root) -> usize {
74        if let Some(vec) = self.keypath.get(root) {
75            vec.iter()
76                .skip(self.offset)
77                .filter(|item| self.filters.iter().all(|f| f(item)))
78                .take(self.limit.unwrap_or(usize::MAX))
79                .count()
80        } else {
81            0
82        }
83    }
84
85    pub fn exists(&self, root: &'a Root) -> bool {
86        self.count(root) > 0
87    }
88
89    pub fn first(&self, root: &'a Root) -> Option<&'a Item> {
90        self.execute(root).into_iter().next()
91    }
92}
93
94/// Implemented for keypath types that target `Vec<Item>`, enabling `.query()`.
95/// The keypath and the reference passed to `query()` share the same lifetime.
96pub trait QueryableCollection<'a, Root, Item> {
97    fn query(&'a self) -> CollectionQuery<'a, Root, Item>;
98}
99
100impl<'a, Root, Item> QueryableCollection<'a, Root, Item> for KpType<'a, Root, Vec<Item>> {
101    fn query(&'a self) -> CollectionQuery<'a, Root, Item> {
102        CollectionQuery::new(self)
103    }
104}
105
106// --- Support for KpType<'static, Root, Vec<Item>> (e.g. from #[derive(Kp)]) ---
107
108/// Query builder for collection keypaths with `'static` lifetime (e.g. from #[derive(Kp)]).
109/// Pass the root when calling `execute`, `count`, `exists`, or `first`.
110pub struct CollectionQueryStatic<'q, Root, Item>
111where
112    Root: 'static,
113    Item: 'static,
114{
115    keypath: &'q KpType<'static, Root, Vec<Item>>,
116    filters: Vec<Box<dyn Fn(&Item) -> bool + 'q>>,
117    limit: Option<usize>,
118    offset: usize,
119}
120
121impl<'q, Root: 'static, Item: 'static> CollectionQueryStatic<'q, Root, Item> {
122    pub fn new(keypath: &'q KpType<'static, Root, Vec<Item>>) -> Self {
123        Self {
124            keypath,
125            filters: Vec::new(),
126            limit: None,
127            offset: 0,
128        }
129    }
130
131    pub fn filter<F>(mut self, predicate: F) -> Self
132    where
133        F: Fn(&Item) -> bool + 'q,
134    {
135        self.filters.push(Box::new(predicate));
136        self
137    }
138
139    pub fn limit(mut self, n: usize) -> Self {
140        self.limit = Some(n);
141        self
142    }
143
144    pub fn offset(mut self, n: usize) -> Self {
145        self.offset = n;
146        self
147    }
148
149    pub fn execute<'a>(&self, root: &'a Root) -> Vec<&'a Item> {
150        if let Some(vec) = get_vec_static(self.keypath, root) {
151            let mut result: Vec<&'a Item> = vec
152                .iter()
153                .skip(self.offset)
154                .filter(|item| self.filters.iter().all(|f| f(item)))
155                .collect();
156            if let Some(limit) = self.limit {
157                result.truncate(limit);
158            }
159            result
160        } else {
161            Vec::new()
162        }
163    }
164
165    pub fn count<'a>(&self, root: &'a Root) -> usize {
166        if let Some(vec) = get_vec_static(self.keypath, root) {
167            vec.iter()
168                .skip(self.offset)
169                .filter(|item| self.filters.iter().all(|f| f(item)))
170                .take(self.limit.unwrap_or(usize::MAX))
171                .count()
172        } else {
173            0
174        }
175    }
176
177    pub fn exists<'a>(&self, root: &'a Root) -> bool {
178        self.count(root) > 0
179    }
180
181    pub fn first<'a>(&self, root: &'a Root) -> Option<&'a Item> {
182        self.execute(root).into_iter().next()
183    }
184}
185
186/// Get `&'a Vec<Item>` from a `'static` keypath and `&'a Root`.
187/// Used by [query_par] for parallel operations. Sound because the closure in
188/// `KpType<'static, ...>` is `for<'b> fn(&'b Root) -> Option<&'b Vec<Item>>`.
189#[inline]
190pub(crate) fn get_vec_static<'a, Root: 'static, Item: 'static>(
191    keypath: &KpType<'static, Root, Vec<Item>>,
192    root: &'a Root,
193) -> Option<&'a Vec<Item>> {
194    // The closure in KpType<'static, ...> is for<'b> fn(&'b Root) -> Option<&'b Vec<Item>>,
195    // so it does not store the reference; extending to 'static for the call is sound.
196    let root_static: &'static Root = unsafe { std::mem::transmute(root) };
197    let opt = keypath.get(root_static);
198    unsafe { std::mem::transmute(opt) }
199}
200
201/// Implemented for `KpType<'static, Root, Vec<Item>>` (e.g. from #[derive(Kp)]), enabling `.query()`.
202pub trait QueryableCollectionStatic<Root, Item>
203where
204    Root: 'static,
205    Item: 'static,
206{
207    fn query(&self) -> CollectionQueryStatic<'_, Root, Item>;
208}
209
210impl<Root: 'static, Item: 'static> QueryableCollectionStatic<Root, Item>
211    for KpType<'static, Root, Vec<Item>>
212{
213    fn query(&self) -> CollectionQueryStatic<'_, Root, Item> {
214        CollectionQueryStatic::new(self)
215    }
216}
217
218#[cfg(test)]
219mod tests {
220    use super::{QueryableCollection, *};
221    use rust_key_paths::Kp;
222
223    #[test]
224    fn test_query_dsl() {
225        struct Database {
226            users: Vec<User>,
227        }
228
229        struct User {
230            id: u32,
231            name: String,
232            age: u32,
233            active: bool,
234        }
235
236        // Type annotation so the keypath gets a concrete lifetime tied to this scope
237        let users_kp: KpType<'_, Database, Vec<User>> = Kp::new(
238            |db: &Database| Some(&db.users),
239            |db: &mut Database| Some(&mut db.users),
240        );
241
242        let db = Database {
243            users: vec![
244                User {
245                    id: 1,
246                    name: "Alice".into(),
247                    age: 25,
248                    active: true,
249                },
250                User {
251                    id: 2,
252                    name: "Bob".into(),
253                    age: 30,
254                    active: false,
255                },
256                User {
257                    id: 3,
258                    name: "Charlie".into(),
259                    age: 35,
260                    active: true,
261                },
262                User {
263                    id: 4,
264                    name: "Diana".into(),
265                    age: 28,
266                    active: true,
267                },
268            ],
269        };
270
271        // Query: active users over 26, limit 2 (use trait to disambiguate from QueryableCollectionStatic)
272        let results = QueryableCollection::query(&users_kp)
273            .filter(|u| u.active)
274            .filter(|u| u.age > 26)
275            .limit(2)
276            .execute(&db);
277
278        assert_eq!(results.len(), 2);
279        assert_eq!(results[0].name, "Charlie");
280
281        // Check if any active user exists
282        assert!(QueryableCollection::query(&users_kp).filter(|u| u.active).exists(&db));
283
284        // Count active users
285        let count = QueryableCollection::query(&users_kp).filter(|u| u.active).count(&db);
286        assert_eq!(count, 3);
287    }
288}