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    /// Converts to a standard iterator for further chaining.
261    ///
262    /// # Example
263    ///
264    /// ```ignore
265    /// let custom: Vec<_> = query
266    ///     .into_iter()
267    ///     .map(|item| item.custom_transform())
268    ///     .filter(|x| x.is_valid())
269    ///     .collect();
270    /// ```
271    pub fn into_iter(self) -> I {
272        self.iter
273    }
274}
275
276// Aggregation operations
277impl<'a, T: 'static, I> LazyQuery<'a, T, I>
278where
279    I: Iterator<Item = &'a T> + 'a,
280{
281    /// Computes sum of a field (terminal operation).
282    ///
283    /// # Example
284    ///
285    /// ```ignore
286    /// let total: f64 = LazyQuery::new(&products)
287    ///     .sum_by(Product::price_r());
288    /// ```
289    pub fn sum_by<F>(self, path: KeyPaths<T, F>) -> F
290    where
291        F: Clone + std::ops::Add<Output = F> + Default + 'static,
292        I: 'a,
293    {
294        self.iter
295            .filter_map(move |item| path.get(item).cloned())
296            .fold(F::default(), |acc, val| acc + val)
297    }
298
299    /// Computes average of a float field (terminal operation).
300    ///
301    /// # Example
302    ///
303    /// ```ignore
304    /// let avg = LazyQuery::new(&products)
305    ///     .avg_by(Product::price_r());
306    /// ```
307    pub fn avg_by(self, path: KeyPaths<T, f64>) -> Option<f64>
308    where
309        I: 'a,
310    {
311        let items: Vec<f64> = self
312            .iter
313            .filter_map(move |item| path.get(item).cloned())
314            .collect();
315
316        if items.is_empty() {
317            None
318        } else {
319            Some(items.iter().sum::<f64>() / items.len() as f64)
320        }
321    }
322
323    /// Finds minimum value of a field (terminal operation).
324    ///
325    /// # Example
326    ///
327    /// ```ignore
328    /// let min = LazyQuery::new(&products)
329    ///     .min_by(Product::price_r());
330    /// ```
331    pub fn min_by<F>(self, path: KeyPaths<T, F>) -> Option<F>
332    where
333        F: Ord + Clone + 'static,
334        I: 'a,
335    {
336        self.iter.filter_map(move |item| path.get(item).cloned()).min()
337    }
338
339    /// Finds maximum value of a field (terminal operation).
340    ///
341    /// # Example
342    ///
343    /// ```ignore
344    /// let max = LazyQuery::new(&products)
345    ///     .max_by(Product::price_r());
346    /// ```
347    pub fn max_by<F>(self, path: KeyPaths<T, F>) -> Option<F>
348    where
349        F: Ord + Clone + 'static,
350        I: 'a,
351    {
352        self.iter.filter_map(move |item| path.get(item).cloned()).max()
353    }
354
355    /// Finds minimum float value (terminal operation).
356    pub fn min_by_float(self, path: KeyPaths<T, f64>) -> Option<f64>
357    where
358        I: 'a,
359    {
360        self.iter
361            .filter_map(move |item| path.get(item).cloned())
362            .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
363    }
364
365    /// Finds maximum float value (terminal operation).
366    pub fn max_by_float(self, path: KeyPaths<T, f64>) -> Option<f64>
367    where
368        I: 'a,
369    {
370        self.iter
371            .filter_map(move |item| path.get(item).cloned())
372            .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
373    }
374
375    // DateTime operations for SystemTime (lazy)
376    /// Filter by SystemTime being after a reference time (lazy).
377    ///
378    /// # Arguments
379    ///
380    /// * `path` - The key-path to the SystemTime field
381    /// * `reference` - The reference time to compare against
382    ///
383    /// # Example
384    ///
385    /// ```ignore
386    /// let recent = LazyQuery::new(&events)
387    ///     .where_after_systemtime(Event::timestamp_r(), cutoff_time)
388    ///     .collect::<Vec<_>>();
389    /// ```
390    pub fn where_after_systemtime(self, path: KeyPaths<T, SystemTime>, reference: SystemTime) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a> {
391        self.where_(path, move |time| time > &reference)
392    }
393
394    /// Filter by SystemTime being before a reference time (lazy).
395    ///
396    /// # Arguments
397    ///
398    /// * `path` - The key-path to the SystemTime field
399    /// * `reference` - The reference time to compare against
400    ///
401    /// # Example
402    ///
403    /// ```ignore
404    /// let old = LazyQuery::new(&events)
405    ///     .where_before_systemtime(Event::timestamp_r(), cutoff_time)
406    ///     .collect::<Vec<_>>();
407    /// ```
408    pub fn where_before_systemtime(self, path: KeyPaths<T, SystemTime>, reference: SystemTime) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a> {
409        self.where_(path, move |time| time < &reference)
410    }
411
412    /// Filter by SystemTime being between two times (inclusive, lazy).
413    ///
414    /// # Arguments
415    ///
416    /// * `path` - The key-path to the SystemTime field
417    /// * `start` - The start time
418    /// * `end` - The end time
419    ///
420    /// # Example
421    ///
422    /// ```ignore
423    /// let range = LazyQuery::new(&events)
424    ///     .where_between_systemtime(Event::timestamp_r(), start, end)
425    ///     .collect::<Vec<_>>();
426    /// ```
427    pub fn where_between_systemtime(
428        self,
429        path: KeyPaths<T, SystemTime>,
430        start: SystemTime,
431        end: SystemTime,
432    ) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a> {
433        self.where_(path, move |time| time >= &start && time <= &end)
434    }
435}
436
437// DateTime operations with chrono (only available with datetime feature, lazy)
438#[cfg(feature = "datetime")]
439impl<'a, T: 'static, I> LazyQuery<'a, T, I>
440where
441    I: Iterator<Item = &'a T> + 'a,
442{
443    /// Filter by DateTime being after a reference time (lazy).
444    ///
445    /// # Arguments
446    ///
447    /// * `path` - The key-path to the DateTime field
448    /// * `reference` - The reference time to compare against
449    ///
450    /// # Example
451    ///
452    /// ```ignore
453    /// let recent = LazyQuery::new(&events)
454    ///     .where_after(Event::timestamp_r(), cutoff_time)
455    ///     .collect::<Vec<_>>();
456    /// ```
457    pub fn where_after<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, reference: DateTime<Tz>) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
458    where
459        Tz: TimeZone + 'static,
460        Tz::Offset: std::fmt::Display,
461    {
462        self.where_(path, move |time| time > &reference)
463    }
464
465    /// Filter by DateTime being before a reference time (lazy).
466    ///
467    /// # Arguments
468    ///
469    /// * `path` - The key-path to the DateTime field
470    /// * `reference` - The reference time to compare against
471    ///
472    /// # Example
473    ///
474    /// ```ignore
475    /// let old = LazyQuery::new(&events)
476    ///     .where_before(Event::timestamp_r(), cutoff_time)
477    ///     .collect::<Vec<_>>();
478    /// ```
479    pub fn where_before<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, reference: DateTime<Tz>) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
480    where
481        Tz: TimeZone + 'static,
482        Tz::Offset: std::fmt::Display,
483    {
484        self.where_(path, move |time| time < &reference)
485    }
486
487    /// Filter by DateTime being between two times (inclusive, lazy).
488    ///
489    /// # Arguments
490    ///
491    /// * `path` - The key-path to the DateTime field
492    /// * `start` - The start time
493    /// * `end` - The end time
494    ///
495    /// # Example
496    ///
497    /// ```ignore
498    /// let range = LazyQuery::new(&events)
499    ///     .where_between(Event::timestamp_r(), start, end)
500    ///     .collect::<Vec<_>>();
501    /// ```
502    pub fn where_between<Tz>(
503        self,
504        path: KeyPaths<T, DateTime<Tz>>,
505        start: DateTime<Tz>,
506        end: DateTime<Tz>,
507    ) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
508    where
509        Tz: TimeZone + 'static,
510        Tz::Offset: std::fmt::Display,
511    {
512        self.where_(path, move |time| time >= &start && time <= &end)
513    }
514
515    /// Filter by DateTime being today (lazy).
516    ///
517    /// # Arguments
518    ///
519    /// * `path` - The key-path to the DateTime field
520    /// * `now` - The current DateTime to compare against
521    ///
522    /// # Example
523    ///
524    /// ```ignore
525    /// let today = LazyQuery::new(&events)
526    ///     .where_today(Event::timestamp_r(), Utc::now())
527    ///     .collect::<Vec<_>>();
528    /// ```
529    pub fn where_today<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, now: DateTime<Tz>) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
530    where
531        Tz: TimeZone + 'static,
532        Tz::Offset: std::fmt::Display,
533    {
534        self.where_(path, move |time| {
535            time.date_naive() == now.date_naive()
536        })
537    }
538
539    /// Filter by DateTime year (lazy).
540    ///
541    /// # Arguments
542    ///
543    /// * `path` - The key-path to the DateTime field
544    /// * `year` - The year to filter by
545    ///
546    /// # Example
547    ///
548    /// ```ignore
549    /// let this_year = LazyQuery::new(&events)
550    ///     .where_year(Event::timestamp_r(), 2024)
551    ///     .collect::<Vec<_>>();
552    /// ```
553    pub fn where_year<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, year: i32) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
554    where
555        Tz: TimeZone + 'static,
556        Tz::Offset: std::fmt::Display,
557    {
558        use chrono::Datelike;
559        self.where_(path, move |time| time.year() == year)
560    }
561
562    /// Filter by DateTime month (lazy).
563    ///
564    /// # Arguments
565    ///
566    /// * `path` - The key-path to the DateTime field
567    /// * `month` - The month to filter by (1-12)
568    ///
569    /// # Example
570    ///
571    /// ```ignore
572    /// let december = LazyQuery::new(&events)
573    ///     .where_month(Event::timestamp_r(), 12)
574    ///     .collect::<Vec<_>>();
575    /// ```
576    pub fn where_month<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, month: u32) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
577    where
578        Tz: TimeZone + 'static,
579        Tz::Offset: std::fmt::Display,
580    {
581        use chrono::Datelike;
582        self.where_(path, move |time| time.month() == month)
583    }
584
585    /// Filter by DateTime day (lazy).
586    ///
587    /// # Arguments
588    ///
589    /// * `path` - The key-path to the DateTime field
590    /// * `day` - The day to filter by (1-31)
591    ///
592    /// # Example
593    ///
594    /// ```ignore
595    /// let first = LazyQuery::new(&events)
596    ///     .where_day(Event::timestamp_r(), 1)
597    ///     .collect::<Vec<_>>();
598    /// ```
599    pub fn where_day<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, day: u32) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
600    where
601        Tz: TimeZone + 'static,
602        Tz::Offset: std::fmt::Display,
603    {
604        use chrono::Datelike;
605        self.where_(path, move |time| time.day() == day)
606    }
607
608    /// Filter by weekend dates (Saturday and Sunday, lazy).
609    ///
610    /// # Arguments
611    ///
612    /// * `path` - The key-path to the DateTime field
613    ///
614    /// # Example
615    ///
616    /// ```ignore
617    /// let weekend_events = LazyQuery::new(&events)
618    ///     .where_weekend(Event::timestamp_r())
619    ///     .collect::<Vec<_>>();
620    /// ```
621    pub fn where_weekend<Tz>(self, path: KeyPaths<T, DateTime<Tz>>) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
622    where
623        Tz: TimeZone + 'static,
624        Tz::Offset: std::fmt::Display,
625    {
626        use chrono::Datelike;
627        self.where_(path, |time| {
628            let weekday = time.weekday().num_days_from_monday();
629            weekday >= 5
630        })
631    }
632
633    /// Filter by weekday dates (Monday through Friday, lazy).
634    ///
635    /// # Arguments
636    ///
637    /// * `path` - The key-path to the DateTime field
638    ///
639    /// # Example
640    ///
641    /// ```ignore
642    /// let weekday_events = LazyQuery::new(&events)
643    ///     .where_weekday(Event::timestamp_r())
644    ///     .collect::<Vec<_>>();
645    /// ```
646    pub fn where_weekday<Tz>(self, path: KeyPaths<T, DateTime<Tz>>) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
647    where
648        Tz: TimeZone + 'static,
649        Tz::Offset: std::fmt::Display,
650    {
651        use chrono::Datelike;
652        self.where_(path, |time| {
653            let weekday = time.weekday().num_days_from_monday();
654            weekday < 5
655        })
656    }
657
658    /// Filter by business hours (9 AM - 5 PM, lazy).
659    ///
660    /// # Arguments
661    ///
662    /// * `path` - The key-path to the DateTime field
663    ///
664    /// # Example
665    ///
666    /// ```ignore
667    /// let business_hours = LazyQuery::new(&events)
668    ///     .where_business_hours(Event::timestamp_r())
669    ///     .collect::<Vec<_>>();
670    /// ```
671    pub fn where_business_hours<Tz>(self, path: KeyPaths<T, DateTime<Tz>>) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
672    where
673        Tz: TimeZone + 'static,
674        Tz::Offset: std::fmt::Display,
675    {
676        use chrono::Timelike;
677        self.where_(path, |time| {
678            let hour = time.hour();
679            hour >= 9 && hour < 17
680        })
681    }
682}
683
684// Enable using LazyQuery in for loops
685impl<'a, T: 'static, I> IntoIterator for LazyQuery<'a, T, I>
686where
687    I: Iterator<Item = &'a T>,
688{
689    type Item = &'a T;
690    type IntoIter = I;
691
692    fn into_iter(self) -> Self::IntoIter {
693        self.iter
694    }
695}
696