rust_queries_core/
lazy.rs

1//! Lazy query implementation using iterators.
2//!
3//! This module provides lazy evaluation of queries, deferring execution
4//! until results are actually consumed.
5
6use key_paths_core::KeyPaths;
7use std::marker::PhantomData;
8
9/// A lazy query builder that uses iterators for deferred execution.
10///
11/// Unlike the standard `Query`, `LazyQuery` doesn't execute until you call
12/// a terminal operation like `.collect()`, `.count()`, or `.first()`.
13///
14/// # Benefits
15///
16/// - **Deferred execution**: No work until results needed
17/// - **Iterator fusion**: Rust optimizes chained operations
18/// - **Early termination**: `.take()` stops as soon as enough items found
19/// - **Composable**: Build complex queries by composition
20///
21/// # Example
22///
23/// ```ignore
24/// // Nothing executes yet
25/// let query = LazyQuery::new(&products)
26///     .where_(Product::price_r(), |&p| p < 100.0)
27///     .where_(Product::stock_r(), |&s| s > 0);
28///
29/// // Execution happens here
30/// let results: Vec<_> = query.collect();
31/// ```
32pub struct LazyQuery<'a, T: 'static, I>
33where
34    I: Iterator<Item = &'a T>,
35{
36    iter: I,
37    _phantom: PhantomData<&'a T>,
38}
39
40impl<'a, T: 'static> LazyQuery<'a, T, std::slice::Iter<'a, T>> {
41    /// Creates a new lazy query from a slice.
42    ///
43    /// # Example
44    ///
45    /// ```ignore
46    /// let query = LazyQuery::new(&products);
47    /// ```
48    pub fn new(data: &'a [T]) -> Self {
49        Self {
50            iter: data.iter(),
51            _phantom: PhantomData,
52        }
53    }
54}
55
56impl<'a, T: 'static, I> LazyQuery<'a, T, I>
57where
58    I: Iterator<Item = &'a T> + 'a,
59{
60    /// Adds a filter predicate (lazy - not executed yet).
61    ///
62    /// # Example
63    ///
64    /// ```ignore
65    /// let query = LazyQuery::new(&products)
66    ///     .where_(Product::price_r(), |&p| p < 100.0);
67    /// ```
68    pub fn where_<F, P>(self, path: KeyPaths<T, F>, predicate: P) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
69    where
70        F: 'static,
71        P: Fn(&F) -> bool + 'a,
72    {
73        LazyQuery {
74            iter: self.iter.filter(move |item| {
75                path.get(item).map_or(false, |val| predicate(val))
76            }),
77            _phantom: PhantomData,
78        }
79    }
80
81    /// Maps each item through a transformation (lazy).
82    ///
83    /// # Example
84    ///
85    /// ```ignore
86    /// let prices = LazyQuery::new(&products)
87    ///     .map_items(|p| p.price)
88    ///     .collect::<Vec<_>>();
89    /// ```
90    pub fn map_items<F, O>(self, f: F) -> impl Iterator<Item = O> + 'a
91    where
92        F: Fn(&'a T) -> O + 'a,
93        I: 'a,
94    {
95        self.iter.map(f)
96    }
97
98    /// Selects/projects a field value (lazy).
99    ///
100    /// Returns iterator over cloned field values.
101    ///
102    /// # Example
103    ///
104    /// ```ignore
105    /// let names: Vec<String> = LazyQuery::new(&products)
106    ///     .select_lazy(Product::name_r())
107    ///     .collect();
108    /// ```
109    pub fn select_lazy<F>(self, path: KeyPaths<T, F>) -> impl Iterator<Item = F> + 'a
110    where
111        F: Clone + 'static,
112        I: 'a,
113    {
114        self.iter.filter_map(move |item| path.get(item).cloned())
115    }
116
117    /// Takes at most `n` items (lazy).
118    ///
119    /// # Example
120    ///
121    /// ```ignore
122    /// let first_10: Vec<_> = LazyQuery::new(&products)
123    ///     .take_lazy(10)
124    ///     .collect();
125    /// ```
126    pub fn take_lazy(self, n: usize) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
127    where
128        I: 'a,
129    {
130        LazyQuery {
131            iter: self.iter.take(n),
132            _phantom: PhantomData,
133        }
134    }
135
136    /// Skips `n` items (lazy).
137    ///
138    /// # Example
139    ///
140    /// ```ignore
141    /// let page_2: Vec<_> = LazyQuery::new(&products)
142    ///     .skip_lazy(10)
143    ///     .take_lazy(10)
144    ///     .collect();
145    /// ```
146    pub fn skip_lazy(self, n: usize) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
147    where
148        I: 'a,
149    {
150        LazyQuery {
151            iter: self.iter.skip(n),
152            _phantom: PhantomData,
153        }
154    }
155
156    /// Collects all items into a vector (terminal operation - executes query).
157    ///
158    /// # Example
159    ///
160    /// ```ignore
161    /// let results: Vec<&Product> = query.collect();
162    /// ```
163    pub fn collect(self) -> Vec<&'a T> {
164        self.iter.collect()
165    }
166
167    /// Gets the first item (terminal operation - executes until first match).
168    ///
169    /// # Example
170    ///
171    /// ```ignore
172    /// let first = query.first();
173    /// ```
174    pub fn first(mut self) -> Option<&'a T> {
175        self.iter.next()
176    }
177
178    /// Counts items (terminal operation - executes query).
179    ///
180    /// # Example
181    ///
182    /// ```ignore
183    /// let count = query.count();
184    /// ```
185    pub fn count(self) -> usize {
186        self.iter.count()
187    }
188
189    /// Checks if any items match (terminal operation - short-circuits).
190    ///
191    /// # Example
192    ///
193    /// ```ignore
194    /// let exists = query.any();
195    /// ```
196    pub fn any(mut self) -> bool {
197        self.iter.next().is_some()
198    }
199
200    /// Executes a function for each item (terminal operation).
201    ///
202    /// # Example
203    ///
204    /// ```ignore
205    /// query.for_each(|item| println!("{:?}", item));
206    /// ```
207    pub fn for_each<F>(self, f: F)
208    where
209        F: FnMut(&'a T),
210    {
211        self.iter.for_each(f)
212    }
213
214    /// Folds the iterator (terminal operation).
215    ///
216    /// # Example
217    ///
218    /// ```ignore
219    /// let sum = query.fold(0.0, |acc, item| acc + item.price);
220    /// ```
221    pub fn fold<B, F>(self, init: B, f: F) -> B
222    where
223        F: FnMut(B, &'a T) -> B,
224    {
225        self.iter.fold(init, f)
226    }
227
228    /// Finds an item matching a predicate (terminal - short-circuits).
229    ///
230    /// # Example
231    ///
232    /// ```ignore
233    /// let found = query.find(|item| item.id == 42);
234    /// ```
235    pub fn find<P>(mut self, predicate: P) -> Option<&'a T>
236    where
237        P: FnMut(&&'a T) -> bool,
238    {
239        self.iter.find(predicate)
240    }
241
242    /// Checks if all items match a predicate (terminal - short-circuits).
243    ///
244    /// # Example
245    ///
246    /// ```ignore
247    /// let all_positive = query.all_match(|item| item.value > 0);
248    /// ```
249    pub fn all_match<P>(mut self, mut predicate: P) -> bool
250    where
251        P: FnMut(&'a T) -> bool,
252    {
253        self.iter.all(move |item| predicate(item))
254    }
255
256    /// Converts to a standard iterator for further chaining.
257    ///
258    /// # Example
259    ///
260    /// ```ignore
261    /// let custom: Vec<_> = query
262    ///     .into_iter()
263    ///     .map(|item| item.custom_transform())
264    ///     .filter(|x| x.is_valid())
265    ///     .collect();
266    /// ```
267    pub fn into_iter(self) -> I {
268        self.iter
269    }
270}
271
272// Aggregation operations
273impl<'a, T: 'static, I> LazyQuery<'a, T, I>
274where
275    I: Iterator<Item = &'a T> + 'a,
276{
277    /// Computes sum of a field (terminal operation).
278    ///
279    /// # Example
280    ///
281    /// ```ignore
282    /// let total: f64 = LazyQuery::new(&products)
283    ///     .sum_by(Product::price_r());
284    /// ```
285    pub fn sum_by<F>(self, path: KeyPaths<T, F>) -> F
286    where
287        F: Clone + std::ops::Add<Output = F> + Default + 'static,
288        I: 'a,
289    {
290        self.iter
291            .filter_map(move |item| path.get(item).cloned())
292            .fold(F::default(), |acc, val| acc + val)
293    }
294
295    /// Computes average of a float field (terminal operation).
296    ///
297    /// # Example
298    ///
299    /// ```ignore
300    /// let avg = LazyQuery::new(&products)
301    ///     .avg_by(Product::price_r());
302    /// ```
303    pub fn avg_by(self, path: KeyPaths<T, f64>) -> Option<f64>
304    where
305        I: 'a,
306    {
307        let items: Vec<f64> = self
308            .iter
309            .filter_map(move |item| path.get(item).cloned())
310            .collect();
311
312        if items.is_empty() {
313            None
314        } else {
315            Some(items.iter().sum::<f64>() / items.len() as f64)
316        }
317    }
318
319    /// Finds minimum value of a field (terminal operation).
320    ///
321    /// # Example
322    ///
323    /// ```ignore
324    /// let min = LazyQuery::new(&products)
325    ///     .min_by(Product::price_r());
326    /// ```
327    pub fn min_by<F>(self, path: KeyPaths<T, F>) -> Option<F>
328    where
329        F: Ord + Clone + 'static,
330        I: 'a,
331    {
332        self.iter.filter_map(move |item| path.get(item).cloned()).min()
333    }
334
335    /// Finds maximum value of a field (terminal operation).
336    ///
337    /// # Example
338    ///
339    /// ```ignore
340    /// let max = LazyQuery::new(&products)
341    ///     .max_by(Product::price_r());
342    /// ```
343    pub fn max_by<F>(self, path: KeyPaths<T, F>) -> Option<F>
344    where
345        F: Ord + Clone + 'static,
346        I: 'a,
347    {
348        self.iter.filter_map(move |item| path.get(item).cloned()).max()
349    }
350
351    /// Finds minimum float value (terminal operation).
352    pub fn min_by_float(self, path: KeyPaths<T, f64>) -> Option<f64>
353    where
354        I: 'a,
355    {
356        self.iter
357            .filter_map(move |item| path.get(item).cloned())
358            .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
359    }
360
361    /// Finds maximum float value (terminal operation).
362    pub fn max_by_float(self, path: KeyPaths<T, f64>) -> Option<f64>
363    where
364        I: 'a,
365    {
366        self.iter
367            .filter_map(move |item| path.get(item).cloned())
368            .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
369    }
370}
371
372// Enable using LazyQuery in for loops
373impl<'a, T: 'static, I> IntoIterator for LazyQuery<'a, T, I>
374where
375    I: Iterator<Item = &'a T>,
376{
377    type Item = &'a T;
378    type IntoIter = I;
379
380    fn into_iter(self) -> Self::IntoIter {
381        self.iter
382    }
383}
384