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;
8use std::time::SystemTime;
9
10#[cfg(feature = "datetime")]
11use chrono::{DateTime, TimeZone};
12
13/// A lazy query builder that uses iterators for deferred execution.
14///
15/// Unlike the standard `Query`, `LazyQuery` doesn't execute until you call
16/// a terminal operation like `.collect()`, `.count()`, or `.first()`.
17///
18/// # Benefits
19///
20/// - **Deferred execution**: No work until results needed
21/// - **Iterator fusion**: Rust optimizes chained operations
22/// - **Early termination**: `.take()` stops as soon as enough items found
23/// - **Composable**: Build complex queries by composition
24///
25/// # Example
26///
27/// ```ignore
28/// // Nothing executes yet
29/// let query = LazyQuery::new(&products)
30///     .where_(Product::price_r(), |&p| p < 100.0)
31///     .where_(Product::stock_r(), |&s| s > 0);
32///
33/// // Execution happens here
34/// let results: Vec<_> = query.collect();
35/// ```
36pub struct LazyQuery<'a, T: 'static, I>
37where
38    I: Iterator<Item = &'a T>,
39{
40    iter: I,
41    _phantom: PhantomData<&'a T>,
42}
43
44impl<'a, T: 'static> LazyQuery<'a, T, std::slice::Iter<'a, T>> {
45    /// Creates a new lazy query from a slice.
46    ///
47    /// # Example
48    ///
49    /// ```ignore
50    /// let query = LazyQuery::new(&products);
51    /// ```
52    pub fn new(data: &'a [T]) -> Self {
53        Self {
54            iter: data.iter(),
55            _phantom: PhantomData,
56        }
57    }
58}
59
60impl<'a, T: 'static, I> LazyQuery<'a, T, I>
61where
62    I: Iterator<Item = &'a T> + 'a,
63{
64    /// Adds a filter predicate (lazy - not executed yet).
65    ///
66    /// # Example
67    ///
68    /// ```ignore
69    /// let query = LazyQuery::new(&products)
70    ///     .where_(Product::price_r(), |&p| p < 100.0);
71    /// ```
72    pub fn where_<F, P>(self, path: KeyPaths<T, F>, predicate: P) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
73    where
74        F: 'static,
75        P: Fn(&F) -> bool + 'a,
76    {
77        LazyQuery {
78            iter: self.iter.filter(move |item| {
79                path.get(item).map_or(false, |val| predicate(val))
80            }),
81            _phantom: PhantomData,
82        }
83    }
84
85    /// Maps each item through a transformation (lazy).
86    ///
87    /// # Example
88    ///
89    /// ```ignore
90    /// let prices = LazyQuery::new(&products)
91    ///     .map_items(|p| p.price)
92    ///     .collect::<Vec<_>>();
93    /// ```
94    pub fn map_items<F, O>(self, f: F) -> impl Iterator<Item = O> + 'a
95    where
96        F: Fn(&'a T) -> O + 'a,
97        I: 'a,
98    {
99        self.iter.map(f)
100    }
101
102    /// Selects/projects a field value (lazy).
103    ///
104    /// Returns iterator over cloned field values.
105    ///
106    /// # Example
107    ///
108    /// ```ignore
109    /// let names: Vec<String> = LazyQuery::new(&products)
110    ///     .select_lazy(Product::name_r())
111    ///     .collect();
112    /// ```
113    pub fn select_lazy<F>(self, path: KeyPaths<T, F>) -> impl Iterator<Item = F> + 'a
114    where
115        F: Clone + 'static,
116        I: 'a,
117    {
118        self.iter.filter_map(move |item| path.get(item).cloned())
119    }
120
121    /// Takes at most `n` items (lazy).
122    ///
123    /// # Example
124    ///
125    /// ```ignore
126    /// let first_10: Vec<_> = LazyQuery::new(&products)
127    ///     .take_lazy(10)
128    ///     .collect();
129    /// ```
130    pub fn take_lazy(self, n: usize) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
131    where
132        I: 'a,
133    {
134        LazyQuery {
135            iter: self.iter.take(n),
136            _phantom: PhantomData,
137        }
138    }
139
140    /// Skips `n` items (lazy).
141    ///
142    /// # Example
143    ///
144    /// ```ignore
145    /// let page_2: Vec<_> = LazyQuery::new(&products)
146    ///     .skip_lazy(10)
147    ///     .take_lazy(10)
148    ///     .collect();
149    /// ```
150    pub fn skip_lazy(self, n: usize) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
151    where
152        I: 'a,
153    {
154        LazyQuery {
155            iter: self.iter.skip(n),
156            _phantom: PhantomData,
157        }
158    }
159
160    /// Collects all items into a vector (terminal operation - executes query).
161    ///
162    /// # Example
163    ///
164    /// ```ignore
165    /// let results: Vec<&Product> = query.collect();
166    /// ```
167    pub fn collect(self) -> Vec<&'a T> {
168        self.iter.collect()
169    }
170
171    /// Gets the first item (terminal operation - executes until first match).
172    ///
173    /// # Example
174    ///
175    /// ```ignore
176    /// let first = query.first();
177    /// ```
178    pub fn first(mut self) -> Option<&'a T> {
179        self.iter.next()
180    }
181
182    /// Counts items (terminal operation - executes query).
183    ///
184    /// # Example
185    ///
186    /// ```ignore
187    /// let count = query.count();
188    /// ```
189    pub fn count(self) -> usize {
190        self.iter.count()
191    }
192
193    /// Checks if any items match (terminal operation - short-circuits).
194    ///
195    /// # Example
196    ///
197    /// ```ignore
198    /// let exists = query.any();
199    /// ```
200    pub fn any(mut self) -> bool {
201        self.iter.next().is_some()
202    }
203
204    /// Executes a function for each item (terminal operation).
205    ///
206    /// # Example
207    ///
208    /// ```ignore
209    /// query.for_each(|item| println!("{:?}", item));
210    /// ```
211    pub fn for_each<F>(self, f: F)
212    where
213        F: FnMut(&'a T),
214    {
215        self.iter.for_each(f)
216    }
217
218    /// Folds the iterator (terminal operation).
219    ///
220    /// # Example
221    ///
222    /// ```ignore
223    /// let sum = query.fold(0.0, |acc, item| acc + item.price);
224    /// ```
225    pub fn fold<B, F>(self, init: B, f: F) -> B
226    where
227        F: FnMut(B, &'a T) -> B,
228    {
229        self.iter.fold(init, f)
230    }
231
232    /// Finds an item matching a predicate (terminal - short-circuits).
233    ///
234    /// # Example
235    ///
236    /// ```ignore
237    /// let found = query.find(|item| item.id == 42);
238    /// ```
239    pub fn find<P>(mut self, predicate: P) -> Option<&'a T>
240    where
241        P: FnMut(&&'a T) -> bool,
242    {
243        self.iter.find(predicate)
244    }
245
246    /// Checks if all items match a predicate (terminal - short-circuits).
247    ///
248    /// # Example
249    ///
250    /// ```ignore
251    /// let all_positive = query.all_match(|item| item.value > 0);
252    /// ```
253    pub fn all_match<P>(mut self, mut predicate: P) -> bool
254    where
255        P: FnMut(&'a T) -> bool,
256    {
257        self.iter.all(move |item| predicate(item))
258    }
259
260    /// Collects all items into a vector (terminal operation - executes query).
261    ///
262    /// # Example
263    ///
264    /// ```ignore
265    /// let results: Vec<&Product> = query.all();
266    /// ```
267    pub fn all(self) -> Vec<&'a T> {
268        self.iter.collect()
269    }
270
271    /// Converts to a standard iterator for further chaining.
272    ///
273    /// # Example
274    ///
275    /// ```ignore
276    /// let custom: Vec<_> = query
277    ///     .into_iter()
278    ///     .map(|item| item.custom_transform())
279    ///     .filter(|x| x.is_valid())
280    ///     .collect();
281    /// ```
282    pub fn into_iter(self) -> I {
283        self.iter
284    }
285}
286
287// Aggregation operations
288impl<'a, T: 'static, I> LazyQuery<'a, T, I>
289where
290    I: Iterator<Item = &'a T> + 'a,
291{
292    /// Computes sum of a field (terminal operation).
293    ///
294    /// # Example
295    ///
296    /// ```ignore
297    /// let total: f64 = LazyQuery::new(&products)
298    ///     .sum_by(Product::price_r());
299    /// ```
300    pub fn sum_by<F>(self, path: KeyPaths<T, F>) -> F
301    where
302        F: Clone + std::ops::Add<Output = F> + Default + 'static,
303        I: 'a,
304    {
305        self.iter
306            .filter_map(move |item| path.get(item).cloned())
307            .fold(F::default(), |acc, val| acc + val)
308    }
309
310    /// Computes average of a float field (terminal operation).
311    ///
312    /// # Example
313    ///
314    /// ```ignore
315    /// let avg = LazyQuery::new(&products)
316    ///     .avg_by(Product::price_r());
317    /// ```
318    pub fn avg_by(self, path: KeyPaths<T, f64>) -> Option<f64>
319    where
320        I: 'a,
321    {
322        let items: Vec<f64> = self
323            .iter
324            .filter_map(move |item| path.get(item).cloned())
325            .collect();
326
327        if items.is_empty() {
328            None
329        } else {
330            Some(items.iter().sum::<f64>() / items.len() as f64)
331        }
332    }
333
334    /// Finds minimum value of a field (terminal operation).
335    ///
336    /// # Example
337    ///
338    /// ```ignore
339    /// let min = LazyQuery::new(&products)
340    ///     .min_by(Product::price_r());
341    /// ```
342    pub fn min_by<F>(self, path: KeyPaths<T, F>) -> Option<F>
343    where
344        F: Ord + Clone + 'static,
345        I: 'a,
346    {
347        self.iter.filter_map(move |item| path.get(item).cloned()).min()
348    }
349
350    /// Finds maximum value of a field (terminal operation).
351    ///
352    /// # Example
353    ///
354    /// ```ignore
355    /// let max = LazyQuery::new(&products)
356    ///     .max_by(Product::price_r());
357    /// ```
358    pub fn max_by<F>(self, path: KeyPaths<T, F>) -> Option<F>
359    where
360        F: Ord + Clone + 'static,
361        I: 'a,
362    {
363        self.iter.filter_map(move |item| path.get(item).cloned()).max()
364    }
365
366    /// Finds minimum float value (terminal operation).
367    pub fn min_by_float(self, path: KeyPaths<T, f64>) -> Option<f64>
368    where
369        I: 'a,
370    {
371        self.iter
372            .filter_map(move |item| path.get(item).cloned())
373            .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
374    }
375
376    /// Finds maximum float value (terminal operation).
377    pub fn max_by_float(self, path: KeyPaths<T, f64>) -> Option<f64>
378    where
379        I: 'a,
380    {
381        self.iter
382            .filter_map(move |item| path.get(item).cloned())
383            .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
384    }
385
386    // DateTime operations for SystemTime (lazy)
387    /// Filter by SystemTime being after a reference time (lazy).
388    ///
389    /// # Arguments
390    ///
391    /// * `path` - The key-path to the SystemTime field
392    /// * `reference` - The reference time to compare against
393    ///
394    /// # Example
395    ///
396    /// ```ignore
397    /// let recent = LazyQuery::new(&events)
398    ///     .where_after_systemtime(Event::timestamp_r(), cutoff_time)
399    ///     .collect::<Vec<_>>();
400    /// ```
401    pub fn where_after_systemtime(self, path: KeyPaths<T, SystemTime>, reference: SystemTime) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a> {
402        self.where_(path, move |time| time > &reference)
403    }
404
405    /// Filter by SystemTime being before a reference time (lazy).
406    ///
407    /// # Arguments
408    ///
409    /// * `path` - The key-path to the SystemTime field
410    /// * `reference` - The reference time to compare against
411    ///
412    /// # Example
413    ///
414    /// ```ignore
415    /// let old = LazyQuery::new(&events)
416    ///     .where_before_systemtime(Event::timestamp_r(), cutoff_time)
417    ///     .collect::<Vec<_>>();
418    /// ```
419    pub fn where_before_systemtime(self, path: KeyPaths<T, SystemTime>, reference: SystemTime) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a> {
420        self.where_(path, move |time| time < &reference)
421    }
422
423    /// Filter by SystemTime being between two times (inclusive, lazy).
424    ///
425    /// # Arguments
426    ///
427    /// * `path` - The key-path to the SystemTime field
428    /// * `start` - The start time
429    /// * `end` - The end time
430    ///
431    /// # Example
432    ///
433    /// ```ignore
434    /// let range = LazyQuery::new(&events)
435    ///     .where_between_systemtime(Event::timestamp_r(), start, end)
436    ///     .collect::<Vec<_>>();
437    /// ```
438    pub fn where_between_systemtime(
439        self,
440        path: KeyPaths<T, SystemTime>,
441        start: SystemTime,
442        end: SystemTime,
443    ) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a> {
444        self.where_(path, move |time| time >= &start && time <= &end)
445    }
446}
447
448// DateTime operations with chrono (only available with datetime feature, lazy)
449#[cfg(feature = "datetime")]
450impl<'a, T: 'static, I> LazyQuery<'a, T, I>
451where
452    I: Iterator<Item = &'a T> + 'a,
453{
454    /// Filter by DateTime being after a reference time (lazy).
455    ///
456    /// # Arguments
457    ///
458    /// * `path` - The key-path to the DateTime field
459    /// * `reference` - The reference time to compare against
460    ///
461    /// # Example
462    ///
463    /// ```ignore
464    /// let recent = LazyQuery::new(&events)
465    ///     .where_after(Event::timestamp_r(), cutoff_time)
466    ///     .collect::<Vec<_>>();
467    /// ```
468    pub fn where_after<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, reference: DateTime<Tz>) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
469    where
470        Tz: TimeZone + 'static,
471        Tz::Offset: std::fmt::Display,
472    {
473        self.where_(path, move |time| time > &reference)
474    }
475
476    /// Filter by DateTime being before a reference time (lazy).
477    ///
478    /// # Arguments
479    ///
480    /// * `path` - The key-path to the DateTime field
481    /// * `reference` - The reference time to compare against
482    ///
483    /// # Example
484    ///
485    /// ```ignore
486    /// let old = LazyQuery::new(&events)
487    ///     .where_before(Event::timestamp_r(), cutoff_time)
488    ///     .collect::<Vec<_>>();
489    /// ```
490    pub fn where_before<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, reference: DateTime<Tz>) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
491    where
492        Tz: TimeZone + 'static,
493        Tz::Offset: std::fmt::Display,
494    {
495        self.where_(path, move |time| time < &reference)
496    }
497
498    /// Filter by DateTime being between two times (inclusive, lazy).
499    ///
500    /// # Arguments
501    ///
502    /// * `path` - The key-path to the DateTime field
503    /// * `start` - The start time
504    /// * `end` - The end time
505    ///
506    /// # Example
507    ///
508    /// ```ignore
509    /// let range = LazyQuery::new(&events)
510    ///     .where_between(Event::timestamp_r(), start, end)
511    ///     .collect::<Vec<_>>();
512    /// ```
513    pub fn where_between<Tz>(
514        self,
515        path: KeyPaths<T, DateTime<Tz>>,
516        start: DateTime<Tz>,
517        end: DateTime<Tz>,
518    ) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
519    where
520        Tz: TimeZone + 'static,
521        Tz::Offset: std::fmt::Display,
522    {
523        self.where_(path, move |time| time >= &start && time <= &end)
524    }
525
526    /// Filter by DateTime being today (lazy).
527    ///
528    /// # Arguments
529    ///
530    /// * `path` - The key-path to the DateTime field
531    /// * `now` - The current DateTime to compare against
532    ///
533    /// # Example
534    ///
535    /// ```ignore
536    /// let today = LazyQuery::new(&events)
537    ///     .where_today(Event::timestamp_r(), Utc::now())
538    ///     .collect::<Vec<_>>();
539    /// ```
540    pub fn where_today<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, now: DateTime<Tz>) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
541    where
542        Tz: TimeZone + 'static,
543        Tz::Offset: std::fmt::Display,
544    {
545        self.where_(path, move |time| {
546            time.date_naive() == now.date_naive()
547        })
548    }
549
550    /// Filter by DateTime year (lazy).
551    ///
552    /// # Arguments
553    ///
554    /// * `path` - The key-path to the DateTime field
555    /// * `year` - The year to filter by
556    ///
557    /// # Example
558    ///
559    /// ```ignore
560    /// let this_year = LazyQuery::new(&events)
561    ///     .where_year(Event::timestamp_r(), 2024)
562    ///     .collect::<Vec<_>>();
563    /// ```
564    pub fn where_year<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, year: i32) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
565    where
566        Tz: TimeZone + 'static,
567        Tz::Offset: std::fmt::Display,
568    {
569        use chrono::Datelike;
570        self.where_(path, move |time| time.year() == year)
571    }
572
573    /// Filter by DateTime month (lazy).
574    ///
575    /// # Arguments
576    ///
577    /// * `path` - The key-path to the DateTime field
578    /// * `month` - The month to filter by (1-12)
579    ///
580    /// # Example
581    ///
582    /// ```ignore
583    /// let december = LazyQuery::new(&events)
584    ///     .where_month(Event::timestamp_r(), 12)
585    ///     .collect::<Vec<_>>();
586    /// ```
587    pub fn where_month<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, month: u32) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
588    where
589        Tz: TimeZone + 'static,
590        Tz::Offset: std::fmt::Display,
591    {
592        use chrono::Datelike;
593        self.where_(path, move |time| time.month() == month)
594    }
595
596    /// Filter by DateTime day (lazy).
597    ///
598    /// # Arguments
599    ///
600    /// * `path` - The key-path to the DateTime field
601    /// * `day` - The day to filter by (1-31)
602    ///
603    /// # Example
604    ///
605    /// ```ignore
606    /// let first = LazyQuery::new(&events)
607    ///     .where_day(Event::timestamp_r(), 1)
608    ///     .collect::<Vec<_>>();
609    /// ```
610    pub fn where_day<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, day: u32) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
611    where
612        Tz: TimeZone + 'static,
613        Tz::Offset: std::fmt::Display,
614    {
615        use chrono::Datelike;
616        self.where_(path, move |time| time.day() == day)
617    }
618
619    /// Filter by weekend dates (Saturday and Sunday, lazy).
620    ///
621    /// # Arguments
622    ///
623    /// * `path` - The key-path to the DateTime field
624    ///
625    /// # Example
626    ///
627    /// ```ignore
628    /// let weekend_events = LazyQuery::new(&events)
629    ///     .where_weekend(Event::timestamp_r())
630    ///     .collect::<Vec<_>>();
631    /// ```
632    pub fn where_weekend<Tz>(self, path: KeyPaths<T, DateTime<Tz>>) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
633    where
634        Tz: TimeZone + 'static,
635        Tz::Offset: std::fmt::Display,
636    {
637        use chrono::Datelike;
638        self.where_(path, |time| {
639            let weekday = time.weekday().num_days_from_monday();
640            weekday >= 5
641        })
642    }
643
644    /// Filter by weekday dates (Monday through Friday, lazy).
645    ///
646    /// # Arguments
647    ///
648    /// * `path` - The key-path to the DateTime field
649    ///
650    /// # Example
651    ///
652    /// ```ignore
653    /// let weekday_events = LazyQuery::new(&events)
654    ///     .where_weekday(Event::timestamp_r())
655    ///     .collect::<Vec<_>>();
656    /// ```
657    pub fn where_weekday<Tz>(self, path: KeyPaths<T, DateTime<Tz>>) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
658    where
659        Tz: TimeZone + 'static,
660        Tz::Offset: std::fmt::Display,
661    {
662        use chrono::Datelike;
663        self.where_(path, |time| {
664            let weekday = time.weekday().num_days_from_monday();
665            weekday < 5
666        })
667    }
668
669    /// Filter by business hours (9 AM - 5 PM, lazy).
670    ///
671    /// # Arguments
672    ///
673    /// * `path` - The key-path to the DateTime field
674    ///
675    /// # Example
676    ///
677    /// ```ignore
678    /// let business_hours = LazyQuery::new(&events)
679    ///     .where_business_hours(Event::timestamp_r())
680    ///     .collect::<Vec<_>>();
681    /// ```
682    pub fn where_business_hours<Tz>(self, path: KeyPaths<T, DateTime<Tz>>) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
683    where
684        Tz: TimeZone + 'static,
685        Tz::Offset: std::fmt::Display,
686    {
687        use chrono::Timelike;
688        self.where_(path, |time| {
689            let hour = time.hour();
690            hour >= 9 && hour < 17
691        })
692    }
693}
694
695// Enable using LazyQuery in for loops
696impl<'a, T: 'static, I> IntoIterator for LazyQuery<'a, T, I>
697where
698    I: Iterator<Item = &'a T>,
699{
700    type Item = &'a T;
701    type IntoIter = I;
702
703    fn into_iter(self) -> Self::IntoIter {
704        self.iter
705    }
706}
707