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