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/// - **Logical operators**: Support AND/OR filter composition
25///
26/// # Example
27///
28/// ```ignore
29/// // Nothing executes yet
30/// let query = LazyQuery::new(&products)
31///     .where_(Product::price(), |&p| p < 100.0)
32///     .where_(Product::stock(), |&s| s > 0);
33///
34/// // Execution happens here
35/// let results: Vec<_> = query.collect();
36/// 
37/// // With AND/OR operators
38/// let query = LazyQuery::new(&products)
39///     .where_(Product::price(), |&p| p < 100.0)
40///     .and(Product::stock(), |&s| s > 0)
41///     .or(Product::category(), |c| c == "Premium");
42/// ```
43pub struct LazyQuery<'a, T: 'static, I>
44where
45    I: Iterator<Item = &'a T>,
46{
47    iter: I,
48    filter_groups: Vec<FilterGroup<'a, T>>,
49    _phantom: PhantomData<&'a T>,
50}
51
52/// Represents a group of filters with a logical operator
53enum FilterGroup<'a, T: 'static> {
54    And(Vec<Box<dyn Fn(&T) -> bool + 'a>>),
55    Or(Vec<Box<dyn Fn(&T) -> bool + 'a>>),
56}
57
58impl<'a, T: 'static> FilterGroup<'a, T> {
59    fn evaluate(&self, item: &T) -> bool {
60        match self {
61            FilterGroup::And(filters) => filters.iter().all(|f| f(item)),
62            FilterGroup::Or(filters) => filters.iter().any(|f| f(item)),
63        }
64    }
65}
66
67impl<'a, T: 'static> LazyQuery<'a, T, std::slice::Iter<'a, T>> {
68    /// Creates a new lazy query from a slice.
69    ///
70    /// # Example
71    ///
72    /// ```ignore
73    /// let query = LazyQuery::new(&products);
74    /// ```
75    pub fn new(data: &'a [T]) -> Self {
76        Self {
77            iter: data.iter(),
78            filter_groups: Vec::new(),
79            _phantom: PhantomData,
80        }
81    }
82}
83
84impl<'a, T: 'static, I> LazyQuery<'a, T, I>
85where
86    I: Iterator<Item = &'a T>,
87{
88    /// Creates a new lazy query from an iterator.
89    ///
90    /// This is useful for creating LazyQuery instances from custom iterators
91    /// or for implementing extension traits.
92    ///
93    /// # Example
94    ///
95    /// ```ignore
96    /// let iter = vec![1, 2, 3].iter();
97    /// let query = LazyQuery::from_iter(iter);
98    /// ```
99    pub fn from_iter(iter: I) -> Self {
100        Self {
101            iter,
102            filter_groups: Vec::new(),
103            _phantom: PhantomData,
104        }
105    }
106    
107    /// Applies all filter groups to the iterator
108    /// 
109    /// Filter groups are evaluated as follows:
110    /// - AND groups: all filters in the group must pass
111    /// - OR groups: at least one filter in the group must pass
112    /// - Between groups: if there are both AND and OR groups, items must satisfy
113    ///   either all AND groups OR at least one OR group (if OR groups exist)
114    fn apply_filters(self) -> impl Iterator<Item = &'a T> + 'a
115    where
116        I: 'a,
117    {
118        let filter_groups = self.filter_groups;
119        let (and_groups, or_groups): (Vec<_>, Vec<_>) = filter_groups
120            .into_iter()
121            .partition(|group| matches!(group, FilterGroup::And(_)));
122        
123        self.iter.filter(move |item| {
124            match (and_groups.is_empty(), or_groups.is_empty()) {
125                // Only AND groups: all must pass
126                (false, true) => and_groups.iter().all(|group| group.evaluate(item)),
127                // Only OR groups: at least one must pass
128                (true, false) => or_groups.iter().any(|group| group.evaluate(item)),
129                // Both AND and OR groups: (all AND pass) OR (any OR pass)
130                (false, false) => {
131                    let all_and_pass = and_groups.iter().all(|group| group.evaluate(item));
132                    let any_or_pass = or_groups.iter().any(|group| group.evaluate(item));
133                    all_and_pass || any_or_pass
134                }
135                // No filters: everything passes
136                (true, true) => true,
137            }
138        })
139    }
140}
141
142impl<'a, T: 'static, I> LazyQuery<'a, T, I>
143where
144    I: Iterator<Item = &'a T> + 'a,
145{
146    /// Adds a filter predicate (lazy - not executed yet).
147    /// Multiple `where_` calls are implicitly ANDed together, unless the last group is an OR group,
148    /// in which case `where_` adds to the OR group.
149    ///
150    /// # Example
151    ///
152    /// ```ignore
153    /// let query = LazyQuery::new(&products)
154    ///     .where_(Product::price(), |&p| p < 100.0);
155    /// ```
156    pub fn where_<F, P>(mut self, path: KeyPaths<T, F>, predicate: P) -> Self
157    where
158        F: 'static,
159        P: Fn(&F) -> bool + 'a,
160    {
161        let filter = Box::new(move |item: &T| {
162                path.get(item).map_or(false, |val| predicate(val))
163        });
164        
165        // If the last group is an OR group, add to it; otherwise create/add to AND group
166        match self.filter_groups.last_mut() {
167            Some(FilterGroup::Or(filters)) => {
168                // Add to existing OR group
169                filters.push(filter);
170            }
171            Some(FilterGroup::And(filters)) => {
172                // Add to existing AND group
173                filters.push(filter);
174            }
175            None => {
176                // Create new AND group
177                self.filter_groups.push(FilterGroup::And(vec![filter]));
178            }
179        }
180        
181        self
182    }
183    
184    /// Adds a filter with AND logic (explicit AND operator).
185    /// This is equivalent to `where_` but makes the AND relationship explicit.
186    ///
187    /// # Example
188    ///
189    /// ```ignore
190    /// let query = LazyQuery::new(&products)
191    ///     .where_(Product::price(), |&p| p < 100.0)
192    ///     .and(Product::stock(), |&s| s > 0);
193    /// ```
194    pub fn and<F, P>(mut self, path: KeyPaths<T, F>, predicate: P) -> Self
195    where
196        F: 'static,
197        P: Fn(&F) -> bool + 'a,
198    {
199        let filter = Box::new(move |item: &T| {
200            path.get(item).map_or(false, |val| predicate(val))
201        });
202        
203        match self.filter_groups.last_mut() {
204            Some(FilterGroup::And(filters)) => {
205                filters.push(filter);
206            }
207            _ => {
208                self.filter_groups.push(FilterGroup::And(vec![filter]));
209            }
210        }
211        
212        self
213    }
214    
215    /// Adds a filter with OR logic (explicit OR operator).
216    /// Items matching this filter OR any filters in the current OR group will pass.
217    ///
218    /// # Example
219    ///
220    /// ```ignore
221    /// let query = LazyQuery::new(&products)
222    ///     .where_(Product::price(), |&p| p < 100.0)
223    ///     .or(Product::category(), |c| c == "Premium");
224    /// ```
225    pub fn or<F, P>(mut self, path: KeyPaths<T, F>, predicate: P) -> Self
226    where
227        F: 'static,
228        P: Fn(&F) -> bool + 'a,
229    {
230        let filter = Box::new(move |item: &T| {
231            path.get(item).map_or(false, |val| predicate(val))
232        });
233        
234        match self.filter_groups.last_mut() {
235            Some(FilterGroup::Or(filters)) => {
236                filters.push(filter);
237            }
238            _ => {
239                self.filter_groups.push(FilterGroup::Or(vec![filter]));
240            }
241        }
242        
243        self
244    }
245
246    /// Maps each item through a transformation (lazy).
247    ///
248    /// # Example
249    ///
250    /// ```ignore
251    /// let prices = LazyQuery::new(&products)
252    ///     .map_items(|p| p.price)
253    ///     .collect::<Vec<_>>();
254    /// ```
255    pub fn map_items<F, O>(self, f: F) -> impl Iterator<Item = O> + 'a
256    where
257        F: Fn(&'a T) -> O + 'a,
258        I: 'a,
259    {
260        self.apply_filters().map(f)
261    }
262
263    /// Selects/projects a field value (lazy).
264    ///
265    /// Returns iterator over cloned field values.
266    ///
267    /// # Example
268    ///
269    /// ```ignore
270    /// let names: Vec<String> = LazyQuery::new(&products)
271    ///     .select_lazy(Product::name())
272    ///     .collect();
273    /// ```
274    pub fn select_lazy<F>(self, path: KeyPaths<T, F>) -> impl Iterator<Item = F> + 'a
275    where
276        F: Clone + 'static,
277        I: 'a,
278    {
279        self.apply_filters().filter_map(move |item| path.get(item).cloned())
280    }
281
282    /// Takes at most `n` items (lazy).
283    ///
284    /// # Example
285    ///
286    /// ```ignore
287    /// let first_10: Vec<_> = LazyQuery::new(&products)
288    ///     .take_lazy(10)
289    ///     .collect();
290    /// ```
291    pub fn take_lazy(self, n: usize) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
292    where
293        I: 'a,
294    {
295        LazyQuery {
296            iter: self.iter.take(n),
297            filter_groups: self.filter_groups,
298            _phantom: PhantomData,
299        }
300    }
301
302    /// Skips `n` items (lazy).
303    ///
304    /// # Example
305    ///
306    /// ```ignore
307    /// let page_2: Vec<_> = LazyQuery::new(&products)
308    ///     .skip_lazy(10)
309    ///     .take_lazy(10)
310    ///     .collect();
311    /// ```
312    pub fn skip_lazy(self, n: usize) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
313    where
314        I: 'a,
315    {
316        LazyQuery {
317            iter: self.iter.skip(n),
318            filter_groups: self.filter_groups,
319            _phantom: PhantomData,
320        }
321    }
322
323    /// Collects all items into a vector (terminal operation - executes query).
324    ///
325    /// # Example
326    ///
327    /// ```ignore
328    /// let results: Vec<&Product> = query.collect();
329    /// ```
330    pub fn collect(self) -> Vec<&'a T>
331    where
332        I: 'a,
333    {
334        self.apply_filters().collect()
335    }
336
337    /// Gets the first item (terminal operation - executes until first match).
338    ///
339    /// # Example
340    ///
341    /// ```ignore
342    /// let first = query.first();
343    /// ```
344    pub fn first(self) -> Option<&'a T>
345    where
346        I: 'a,
347    {
348        self.apply_filters().next()
349    }
350
351    /// Counts items (terminal operation - executes query).
352    ///
353    /// # Example
354    ///
355    /// ```ignore
356    /// let count = query.count();
357    /// ```
358    pub fn count(self) -> usize
359    where
360        I: 'a,
361    {
362        self.apply_filters().count()
363    }
364
365    /// Checks if any items match (terminal operation - short-circuits).
366    ///
367    /// # Example
368    ///
369    /// ```ignore
370    /// let exists = query.any();
371    /// ```
372    pub fn any(self) -> bool
373    where
374        I: 'a,
375    {
376        self.apply_filters().next().is_some()
377    }
378
379    /// Executes a function for each item (terminal operation).
380    ///
381    /// # Example
382    ///
383    /// ```ignore
384    /// query.for_each(|item| println!("{:?}", item));
385    /// ```
386    pub fn for_each<F>(self, f: F)
387    where
388        F: FnMut(&'a T),
389    {
390        self.apply_filters().for_each(f)
391    }
392
393    /// Folds the iterator (terminal operation).
394    ///
395    /// # Example
396    ///
397    /// ```ignore
398    /// let sum = query.fold(0.0, |acc, item| acc + item.price);
399    /// ```
400    pub fn fold<B, F>(self, init: B, f: F) -> B
401    where
402        F: FnMut(B, &'a T) -> B,
403    {
404        self.apply_filters().fold(init, f)
405    }
406
407    /// Finds an item matching a predicate (terminal - short-circuits).
408    ///
409    /// # Example
410    ///
411    /// ```ignore
412    /// let found = query.find(|item| item.id == 42);
413    /// ```
414    pub fn find<P>(self, predicate: P) -> Option<&'a T>
415    where
416        P: FnMut(&&'a T) -> bool,
417        I: 'a,
418    {
419        self.apply_filters().find(predicate)
420    }
421
422    /// Checks if all items match a predicate (terminal - short-circuits).
423    ///
424    /// # Example
425    ///
426    /// ```ignore
427    /// let all_positive = query.all_match(|item| item.value > 0);
428    /// ```
429    pub fn all_match<P>(self, mut predicate: P) -> bool
430    where
431        P: FnMut(&'a T) -> bool,
432        I: 'a,
433    {
434        self.apply_filters().all(move |item| predicate(item))
435    }
436
437    /// Collects all items into a vector (terminal operation - executes query).
438    ///
439    /// # Example
440    ///
441    /// ```ignore
442    /// let results: Vec<&Product> = query.all();
443    /// ```
444    pub fn all(self) -> Vec<&'a T>
445    where
446        I: 'a,
447    {
448        self.apply_filters().collect()
449    }
450
451    /// Converts to a standard iterator for further chaining.
452    ///
453    /// # Example
454    ///
455    /// ```ignore
456    /// let custom: Vec<_> = query
457    ///     .into_iter()
458    ///     .map(|item| item.custom_transform())
459    ///     .filter(|x| x.is_valid())
460    ///     .collect();
461    /// ```
462    pub fn into_iter(self) -> I {
463        self.iter
464    }
465}
466
467// Aggregation operations
468impl<'a, T: 'static, I> LazyQuery<'a, T, I>
469where
470    I: Iterator<Item = &'a T> + 'a,
471{
472    /// Computes sum of a field (terminal operation).
473    ///
474    /// # Example
475    ///
476    /// ```ignore
477    /// let total: f64 = LazyQuery::new(&products)
478    ///     .sum_by(Product::price());
479    /// ```
480    pub fn sum_by<F>(self, path: KeyPaths<T, F>) -> F
481    where
482        F: Clone + std::ops::Add<Output = F> + Default + 'static,
483        I: 'a,
484    {
485        self.apply_filters()
486            .filter_map(move |item| path.get(item).cloned())
487            .fold(F::default(), |acc, val| acc + val)
488    }
489
490    /// Computes average of a float field (terminal operation).
491    ///
492    /// # Example
493    ///
494    /// ```ignore
495    /// let avg = LazyQuery::new(&products)
496    ///     .avg_by(Product::price());
497    /// ```
498    pub fn avg_by(self, path: KeyPaths<T, f64>) -> Option<f64>
499    where
500        I: 'a,
501    {
502        let items: Vec<f64> = self
503            .apply_filters()
504            .filter_map(move |item| path.get(item).cloned())
505            .collect();
506
507        if items.is_empty() {
508            None
509        } else {
510            Some(items.iter().sum::<f64>() / items.len() as f64)
511        }
512    }
513
514    /// Finds minimum value of a field (terminal operation).
515    ///
516    /// # Example
517    ///
518    /// ```ignore
519    /// let min = LazyQuery::new(&products)
520    ///     .min_by(Product::price());
521    /// ```
522    pub fn min_by<F>(self, path: KeyPaths<T, F>) -> Option<F>
523    where
524        F: Ord + Clone + 'static,
525        I: 'a,
526    {
527        self.apply_filters().filter_map(move |item| path.get(item).cloned()).min()
528    }
529
530    /// Finds maximum value of a field (terminal operation).
531    ///
532    /// # Example
533    ///
534    /// ```ignore
535    /// let max = LazyQuery::new(&products)
536    ///     .max_by(Product::price());
537    /// ```
538    pub fn max_by<F>(self, path: KeyPaths<T, F>) -> Option<F>
539    where
540        F: Ord + Clone + 'static,
541        I: 'a,
542    {
543        self.apply_filters().filter_map(move |item| path.get(item).cloned()).max()
544    }
545
546    /// Finds minimum float value (terminal operation).
547    pub fn min_by_float(self, path: KeyPaths<T, f64>) -> Option<f64>
548    where
549        I: 'a,
550    {
551        self.apply_filters()
552            .filter_map(move |item| path.get(item).cloned())
553            .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
554    }
555
556    /// Finds maximum float value (terminal operation).
557    pub fn max_by_float(self, path: KeyPaths<T, f64>) -> Option<f64>
558    where
559        I: 'a,
560    {
561        self.apply_filters()
562            .filter_map(move |item| path.get(item).cloned())
563            .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
564    }
565
566    // DateTime operations for SystemTime (lazy)
567    /// Filter by SystemTime being after a reference time (lazy).
568    ///
569    /// # Arguments
570    ///
571    /// * `path` - The key-path to the SystemTime field
572    /// * `reference` - The reference time to compare against
573    ///
574    /// # Example
575    ///
576    /// ```ignore
577    /// let recent = LazyQuery::new(&events)
578    ///     .where_after_systemtime(Event::timestamp(), cutoff_time)
579    ///     .collect::<Vec<_>>();
580    /// ```
581    pub fn where_after_systemtime(self, path: KeyPaths<T, SystemTime>, reference: SystemTime) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a> {
582        self.where_(path, move |time| time > &reference)
583    }
584
585    /// Filter by SystemTime being before a reference time (lazy).
586    ///
587    /// # Arguments
588    ///
589    /// * `path` - The key-path to the SystemTime field
590    /// * `reference` - The reference time to compare against
591    ///
592    /// # Example
593    ///
594    /// ```ignore
595    /// let old = LazyQuery::new(&events)
596    ///     .where_before_systemtime(Event::timestamp(), cutoff_time)
597    ///     .collect::<Vec<_>>();
598    /// ```
599    pub fn where_before_systemtime(self, path: KeyPaths<T, SystemTime>, reference: SystemTime) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a> {
600        self.where_(path, move |time| time < &reference)
601    }
602
603    /// Filter by SystemTime being between two times (inclusive, lazy).
604    ///
605    /// # Arguments
606    ///
607    /// * `path` - The key-path to the SystemTime field
608    /// * `start` - The start time
609    /// * `end` - The end time
610    ///
611    /// # Example
612    ///
613    /// ```ignore
614    /// let range = LazyQuery::new(&events)
615    ///     .where_between_systemtime(Event::timestamp(), start, end)
616    ///     .collect::<Vec<_>>();
617    /// ```
618    pub fn where_between_systemtime(
619        self,
620        path: KeyPaths<T, SystemTime>,
621        start: SystemTime,
622        end: SystemTime,
623    ) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a> {
624        self.where_(path, move |time| time >= &start && time <= &end)
625    }
626}
627
628// DateTime operations with chrono (only available with datetime feature, lazy)
629#[cfg(feature = "datetime")]
630impl<'a, T: 'static, I> LazyQuery<'a, T, I>
631where
632    I: Iterator<Item = &'a T> + 'a,
633{
634    /// Filter by DateTime being after a reference time (lazy).
635    ///
636    /// # Arguments
637    ///
638    /// * `path` - The key-path to the DateTime field
639    /// * `reference` - The reference time to compare against
640    ///
641    /// # Example
642    ///
643    /// ```ignore
644    /// let recent = LazyQuery::new(&events)
645    ///     .where_after(Event::timestamp(), cutoff_time)
646    ///     .collect::<Vec<_>>();
647    /// ```
648    pub fn where_after<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, reference: DateTime<Tz>) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
649    where
650        Tz: TimeZone + 'static,
651        Tz::Offset: std::fmt::Display,
652    {
653        self.where_(path, move |time| time > &reference)
654    }
655
656    /// Filter by DateTime being before a reference time (lazy).
657    ///
658    /// # Arguments
659    ///
660    /// * `path` - The key-path to the DateTime field
661    /// * `reference` - The reference time to compare against
662    ///
663    /// # Example
664    ///
665    /// ```ignore
666    /// let old = LazyQuery::new(&events)
667    ///     .where_before(Event::timestamp(), cutoff_time)
668    ///     .collect::<Vec<_>>();
669    /// ```
670    pub fn where_before<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, reference: DateTime<Tz>) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
671    where
672        Tz: TimeZone + 'static,
673        Tz::Offset: std::fmt::Display,
674    {
675        self.where_(path, move |time| time < &reference)
676    }
677
678    /// Filter by DateTime being between two times (inclusive, lazy).
679    ///
680    /// # Arguments
681    ///
682    /// * `path` - The key-path to the DateTime field
683    /// * `start` - The start time
684    /// * `end` - The end time
685    ///
686    /// # Example
687    ///
688    /// ```ignore
689    /// let range = LazyQuery::new(&events)
690    ///     .where_between(Event::timestamp(), start, end)
691    ///     .collect::<Vec<_>>();
692    /// ```
693    pub fn where_between<Tz>(
694        self,
695        path: KeyPaths<T, DateTime<Tz>>,
696        start: DateTime<Tz>,
697        end: DateTime<Tz>,
698    ) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
699    where
700        Tz: TimeZone + 'static,
701        Tz::Offset: std::fmt::Display,
702    {
703        self.where_(path, move |time| time >= &start && time <= &end)
704    }
705
706    /// Filter by DateTime being today (lazy).
707    ///
708    /// # Arguments
709    ///
710    /// * `path` - The key-path to the DateTime field
711    /// * `now` - The current DateTime to compare against
712    ///
713    /// # Example
714    ///
715    /// ```ignore
716    /// let today = LazyQuery::new(&events)
717    ///     .where_today(Event::timestamp(), Utc::now())
718    ///     .collect::<Vec<_>>();
719    /// ```
720    pub fn where_today<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, now: DateTime<Tz>) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
721    where
722        Tz: TimeZone + 'static,
723        Tz::Offset: std::fmt::Display,
724    {
725        self.where_(path, move |time| {
726            time.date_naive() == now.date_naive()
727        })
728    }
729
730    /// Filter by DateTime year (lazy).
731    ///
732    /// # Arguments
733    ///
734    /// * `path` - The key-path to the DateTime field
735    /// * `year` - The year to filter by
736    ///
737    /// # Example
738    ///
739    /// ```ignore
740    /// let this_year = LazyQuery::new(&events)
741    ///     .where_year(Event::timestamp(), 2024)
742    ///     .collect::<Vec<_>>();
743    /// ```
744    pub fn where_year<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, year: i32) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
745    where
746        Tz: TimeZone + 'static,
747        Tz::Offset: std::fmt::Display,
748    {
749        use chrono::Datelike;
750        self.where_(path, move |time| time.year() == year)
751    }
752
753    /// Filter by DateTime month (lazy).
754    ///
755    /// # Arguments
756    ///
757    /// * `path` - The key-path to the DateTime field
758    /// * `month` - The month to filter by (1-12)
759    ///
760    /// # Example
761    ///
762    /// ```ignore
763    /// let december = LazyQuery::new(&events)
764    ///     .where_month(Event::timestamp(), 12)
765    ///     .collect::<Vec<_>>();
766    /// ```
767    pub fn where_month<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, month: u32) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
768    where
769        Tz: TimeZone + 'static,
770        Tz::Offset: std::fmt::Display,
771    {
772        use chrono::Datelike;
773        self.where_(path, move |time| time.month() == month)
774    }
775
776    /// Filter by DateTime day (lazy).
777    ///
778    /// # Arguments
779    ///
780    /// * `path` - The key-path to the DateTime field
781    /// * `day` - The day to filter by (1-31)
782    ///
783    /// # Example
784    ///
785    /// ```ignore
786    /// let first = LazyQuery::new(&events)
787    ///     .where_day(Event::timestamp(), 1)
788    ///     .collect::<Vec<_>>();
789    /// ```
790    pub fn where_day<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, day: u32) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
791    where
792        Tz: TimeZone + 'static,
793        Tz::Offset: std::fmt::Display,
794    {
795        use chrono::Datelike;
796        self.where_(path, move |time| time.day() == day)
797    }
798
799    /// Filter by weekend dates (Saturday and Sunday, lazy).
800    ///
801    /// # Arguments
802    ///
803    /// * `path` - The key-path to the DateTime field
804    ///
805    /// # Example
806    ///
807    /// ```ignore
808    /// let weekend_events = LazyQuery::new(&events)
809    ///     .where_weekend(Event::timestamp())
810    ///     .collect::<Vec<_>>();
811    /// ```
812    pub fn where_weekend<Tz>(self, path: KeyPaths<T, DateTime<Tz>>) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
813    where
814        Tz: TimeZone + 'static,
815        Tz::Offset: std::fmt::Display,
816    {
817        use chrono::Datelike;
818        self.where_(path, |time| {
819            let weekday = time.weekday().num_days_from_monday();
820            weekday >= 5
821        })
822    }
823
824    /// Filter by weekday dates (Monday through Friday, lazy).
825    ///
826    /// # Arguments
827    ///
828    /// * `path` - The key-path to the DateTime field
829    ///
830    /// # Example
831    ///
832    /// ```ignore
833    /// let weekday_events = LazyQuery::new(&events)
834    ///     .where_weekday(Event::timestamp())
835    ///     .collect::<Vec<_>>();
836    /// ```
837    pub fn where_weekday<Tz>(self, path: KeyPaths<T, DateTime<Tz>>) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
838    where
839        Tz: TimeZone + 'static,
840        Tz::Offset: std::fmt::Display,
841    {
842        use chrono::Datelike;
843        self.where_(path, |time| {
844            let weekday = time.weekday().num_days_from_monday();
845            weekday < 5
846        })
847    }
848
849    /// Filter by business hours (9 AM - 5 PM, lazy).
850    ///
851    /// # Arguments
852    ///
853    /// * `path` - The key-path to the DateTime field
854    ///
855    /// # Example
856    ///
857    /// ```ignore
858    /// let business_hours = LazyQuery::new(&events)
859    ///     .where_business_hours(Event::timestamp())
860    ///     .collect::<Vec<_>>();
861    /// ```
862    pub fn where_business_hours<Tz>(self, path: KeyPaths<T, DateTime<Tz>>) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
863    where
864        Tz: TimeZone + 'static,
865        Tz::Offset: std::fmt::Display,
866    {
867        use chrono::Timelike;
868        self.where_(path, |time| {
869            let hour = time.hour();
870            hour >= 9 && hour < 17
871        })
872    }
873}
874
875// i64 DateTime Aggregators (Unix timestamps in milliseconds)
876impl<'a, T: 'static, I> LazyQuery<'a, T, I>
877where
878    I: Iterator<Item = &'a T> + 'a,
879{
880    /// Finds minimum i64 timestamp value (terminal operation).
881    ///
882    /// # Example
883    ///
884    /// ```ignore
885    /// let earliest = LazyQuery::new(&events)
886    ///     .min_timestamp(Event::created_at_r());
887    /// ```
888    pub fn min_timestamp(self, path: KeyPaths<T, i64>) -> Option<i64>
889    where
890        I: 'a,
891    {
892        self.apply_filters()
893            .filter_map(move |item| path.get(item).cloned())
894            .min()
895    }
896
897    /// Finds maximum i64 timestamp value (terminal operation).
898    ///
899    /// # Example
900    ///
901    /// ```ignore
902    /// let latest = LazyQuery::new(&events)
903    ///     .max_timestamp(Event::created_at_r());
904    /// ```
905    pub fn max_timestamp(self, path: KeyPaths<T, i64>) -> Option<i64>
906    where
907        I: 'a,
908    {
909        self.apply_filters()
910            .filter_map(move |item| path.get(item).cloned())
911            .max()
912    }
913
914    /// Computes average of i64 timestamp values (terminal operation).
915    ///
916    /// # Example
917    ///
918    /// ```ignore
919    /// let avg = LazyQuery::new(&events)
920    ///     .avg_timestamp(Event::created_at_r());
921    /// ```
922    pub fn avg_timestamp(self, path: KeyPaths<T, i64>) -> Option<i64>
923    where
924        I: 'a,
925    {
926        let items: Vec<i64> = self
927            .apply_filters()
928            .filter_map(move |item| path.get(item).cloned())
929            .collect();
930
931        if items.is_empty() {
932            None
933        } else {
934            Some(items.iter().sum::<i64>() / items.len() as i64)
935        }
936    }
937
938    /// Computes sum of i64 timestamp values (terminal operation).
939    ///
940    /// # Example
941    ///
942    /// ```ignore
943    /// let total = LazyQuery::new(&events)
944    ///     .sum_timestamp(Event::created_at_r());
945    /// ```
946    pub fn sum_timestamp(self, path: KeyPaths<T, i64>) -> i64
947    where
948        I: 'a,
949    {
950        self.apply_filters()
951            .filter_map(move |item| path.get(item).cloned())
952            .sum()
953    }
954
955    /// Counts i64 timestamp values (terminal operation).
956    ///
957    /// # Example
958    ///
959    /// ```ignore
960    /// let count = LazyQuery::new(&events)
961    ///     .count_timestamp(Event::created_at_r());
962    /// ```
963    pub fn count_timestamp(self, path: KeyPaths<T, i64>) -> usize
964    where
965        I: 'a,
966    {
967        self.apply_filters()
968            .filter(move |item| path.get(item).is_some())
969            .count()
970    }
971
972    /// Filter by i64 timestamp being after a reference time (lazy).
973    ///
974    /// # Arguments
975    ///
976    /// * `path` - The key-path to the i64 timestamp field
977    /// * `reference` - The reference timestamp to compare against
978    ///
979    /// # Example
980    ///
981    /// ```ignore
982    /// let recent = LazyQuery::new(&events)
983    ///     .where_after_timestamp(Event::created_at_r(), cutoff_time)
984    ///     .collect::<Vec<_>>();
985    /// ```
986    pub fn where_after_timestamp(self, path: KeyPaths<T, i64>, reference: i64) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a> {
987        self.where_(path, move |timestamp| timestamp > &reference)
988    }
989
990    /// Filter by i64 timestamp being before a reference time (lazy).
991    ///
992    /// # Arguments
993    ///
994    /// * `path` - The key-path to the i64 timestamp field
995    /// * `reference` - The reference timestamp to compare against
996    ///
997    /// # Example
998    ///
999    /// ```ignore
1000    /// let old = LazyQuery::new(&events)
1001    ///     .where_before_timestamp(Event::created_at_r(), cutoff_time)
1002    ///     .collect::<Vec<_>>();
1003    /// ```
1004    pub fn where_before_timestamp(self, path: KeyPaths<T, i64>, reference: i64) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a> {
1005        self.where_(path, move |timestamp| timestamp < &reference)
1006    }
1007
1008    /// Filter by i64 timestamp being between two times (inclusive, lazy).
1009    ///
1010    /// # Arguments
1011    ///
1012    /// * `path` - The key-path to the i64 timestamp field
1013    /// * `start` - The start timestamp
1014    /// * `end` - The end timestamp
1015    ///
1016    /// # Example
1017    ///
1018    /// ```ignore
1019    /// let range = LazyQuery::new(&events)
1020    ///     .where_between_timestamp(Event::created_at_r(), start, end)
1021    ///     .collect::<Vec<_>>();
1022    /// ```
1023    pub fn where_between_timestamp(
1024        self,
1025        path: KeyPaths<T, i64>,
1026        start: i64,
1027        end: i64,
1028    ) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a> {
1029        self.where_(path, move |timestamp| timestamp >= &start && timestamp <= &end)
1030    }
1031
1032    /// Filter by i64 timestamp being within the last N days (lazy).
1033    ///
1034    /// # Arguments
1035    ///
1036    /// * `path` - The key-path to the i64 timestamp field
1037    /// * `days` - Number of days to look back
1038    ///
1039    /// # Example
1040    ///
1041    /// ```ignore
1042    /// let recent = LazyQuery::new(&events)
1043    ///     .where_last_days_timestamp(Event::created_at_r(), 30)
1044    ///     .collect::<Vec<_>>();
1045    /// ```
1046    pub fn where_last_days_timestamp(self, path: KeyPaths<T, i64>, days: i64) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a> {
1047        let now = chrono::Utc::now().timestamp_millis();
1048        let cutoff = now - (days * 24 * 60 * 60 * 1000); // Convert days to milliseconds
1049        self.where_after_timestamp(path, cutoff)
1050    }
1051
1052    /// Filter by i64 timestamp being within the next N days (lazy).
1053    ///
1054    /// # Arguments
1055    ///
1056    /// * `path` - The key-path to the i64 timestamp field
1057    /// * `days` - Number of days to look forward
1058    ///
1059    /// # Example
1060    ///
1061    /// ```ignore
1062    /// let upcoming = LazyQuery::new(&events)
1063    ///     .where_next_days_timestamp(Event::scheduled_at_r(), 7)
1064    ///     .collect::<Vec<_>>();
1065    /// ```
1066    pub fn where_next_days_timestamp(self, path: KeyPaths<T, i64>, days: i64) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a> {
1067        let now = chrono::Utc::now().timestamp_millis();
1068        let cutoff = now + (days * 24 * 60 * 60 * 1000); // Convert days to milliseconds
1069        self.where_before_timestamp(path, cutoff)
1070    }
1071
1072    /// Filter by i64 timestamp being within the last N hours (lazy).
1073    ///
1074    /// # Arguments
1075    ///
1076    /// * `path` - The key-path to the i64 timestamp field
1077    /// * `hours` - Number of hours to look back
1078    ///
1079    /// # Example
1080    ///
1081    /// ```ignore
1082    /// let recent = LazyQuery::new(&events)
1083    ///     .where_last_hours_timestamp(Event::created_at_r(), 24)
1084    ///     .collect::<Vec<_>>();
1085    /// ```
1086    pub fn where_last_hours_timestamp(self, path: KeyPaths<T, i64>, hours: i64) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a> {
1087        let now = chrono::Utc::now().timestamp_millis();
1088        let cutoff = now - (hours * 60 * 60 * 1000); // Convert hours to milliseconds
1089        self.where_after_timestamp(path, cutoff)
1090    }
1091
1092    /// Filter by i64 timestamp being within the next N hours (lazy).
1093    ///
1094    /// # Arguments
1095    ///
1096    /// * `path` - The key-path to the i64 timestamp field
1097    /// * `hours` - Number of hours to look forward
1098    ///
1099    /// # Example
1100    ///
1101    /// ```ignore
1102    /// let upcoming = LazyQuery::new(&events)
1103    ///     .where_next_hours_timestamp(Event::scheduled_at_r(), 2)
1104    ///     .collect::<Vec<_>>();
1105    /// ```
1106    pub fn where_next_hours_timestamp(self, path: KeyPaths<T, i64>, hours: i64) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a> {
1107        let now = chrono::Utc::now().timestamp_millis();
1108        let cutoff = now + (hours * 60 * 60 * 1000); // Convert hours to milliseconds
1109        self.where_before_timestamp(path, cutoff)
1110    }
1111
1112    /// Filter by i64 timestamp being within the last N minutes (lazy).
1113    ///
1114    /// # Arguments
1115    ///
1116    /// * `path` - The key-path to the i64 timestamp field
1117    /// * `minutes` - Number of minutes to look back
1118    ///
1119    /// # Example
1120    ///
1121    /// ```ignore
1122    /// let recent = LazyQuery::new(&events)
1123    ///     .where_last_minutes_timestamp(Event::created_at_r(), 60)
1124    ///     .collect::<Vec<_>>();
1125    /// ```
1126    pub fn where_last_minutes_timestamp(self, path: KeyPaths<T, i64>, minutes: i64) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a> {
1127        let now = chrono::Utc::now().timestamp_millis();
1128        let cutoff = now - (minutes * 60 * 1000); // Convert minutes to milliseconds
1129        self.where_after_timestamp(path, cutoff)
1130    }
1131
1132    /// Filter by i64 timestamp being within the next N minutes (lazy).
1133    ///
1134    /// # Arguments
1135    ///
1136    /// * `path` - The key-path to the i64 timestamp field
1137    /// * `minutes` - Number of minutes to look forward
1138    ///
1139    /// # Example
1140    ///
1141    /// ```ignore
1142    /// let upcoming = LazyQuery::new(&events)
1143    ///     .where_next_minutes_timestamp(Event::scheduled_at_r(), 30)
1144    ///     .collect::<Vec<_>>();
1145    /// ```
1146    pub fn where_next_minutes_timestamp(self, path: KeyPaths<T, i64>, minutes: i64) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a> {
1147        let now = chrono::Utc::now().timestamp_millis();
1148        let cutoff = now + (minutes * 60 * 1000); // Convert minutes to milliseconds
1149        self.where_before_timestamp(path, cutoff)
1150    }
1151}
1152
1153// Enable using LazyQuery in for loops
1154// Note: This consumes the query and applies all filters
1155impl<'a, T: 'static, I> IntoIterator for LazyQuery<'a, T, I>
1156where
1157    I: Iterator<Item = &'a T> + 'a,
1158{
1159    type Item = &'a T;
1160    type IntoIter = Box<dyn Iterator<Item = &'a T> + 'a>;
1161
1162    fn into_iter(self) -> Self::IntoIter {
1163        Box::new(self.apply_filters())
1164    }
1165}
1166
1167#[cfg(test)]
1168mod tests {
1169    use crate::ext::QueryableExt;
1170    use crate::lazy::LazyQuery;
1171    use key_paths_derive::Keypath;
1172
1173    #[derive(Debug, Clone, PartialEq, Keypath)]
1174    struct Product {
1175        id: u32,
1176        name: String,
1177        price: f64,
1178        category: String,
1179        stock: u32,
1180        rating: f64,
1181    }
1182
1183    fn create_test_products() -> Vec<Product> {
1184        vec![
1185            Product {
1186                id: 1,
1187                name: "Laptop".to_string(),
1188                price: 999.99,
1189                category: "Electronics".to_string(),
1190                stock: 5,
1191                rating: 4.5,
1192            },
1193            Product {
1194                id: 2,
1195                name: "Mouse".to_string(),
1196                price: 29.99,
1197                category: "Electronics".to_string(),
1198                stock: 50,
1199                rating: 4.0,
1200            },
1201            Product {
1202                id: 3,
1203                name: "Keyboard".to_string(),
1204                price: 79.99,
1205                category: "Electronics".to_string(),
1206                stock: 30,
1207                rating: 4.8,
1208            },
1209            Product {
1210                id: 4,
1211                name: "Monitor".to_string(),
1212                price: 299.99,
1213                category: "Electronics".to_string(),
1214                stock: 12,
1215                rating: 4.2,
1216            },
1217            Product {
1218                id: 5,
1219                name: "Desk Chair".to_string(),
1220                price: 199.99,
1221                category: "Furniture".to_string(),
1222                stock: 8,
1223                rating: 4.7,
1224            },
1225            Product {
1226                id: 6,
1227                name: "Premium Laptop".to_string(),
1228                price: 1999.99,
1229                category: "Electronics".to_string(),
1230                stock: 3,
1231                rating: 4.9,
1232            },
1233        ]
1234    }
1235
1236    #[test]
1237    fn test_where_implicit_and() {
1238        let products = create_test_products();
1239        
1240        // Multiple where_ calls should be ANDed together
1241        let results: Vec<_> = products
1242            .lazy_query()
1243            .where_(Product::price(), |&p| p < 100.0)
1244            .where_(Product::stock(), |&s| s > 10)
1245            .collect();
1246        
1247        // Should find: Mouse (29.99, stock 50) and Keyboard (79.99, stock 30)
1248        assert_eq!(results.len(), 2);
1249        assert!(results.iter().any(|p| p.name == "Mouse"));
1250        assert!(results.iter().any(|p| p.name == "Keyboard"));
1251    }
1252
1253    #[test]
1254    fn test_explicit_and() {
1255        let products = create_test_products();
1256        
1257        let results: Vec<_> = products
1258            .lazy_query()
1259            .where_(Product::price(), |&p| p < 100.0)
1260            .and(Product::stock(), |&s| s > 10)
1261            .collect();
1262        
1263        // Should find: Mouse and Keyboard
1264        assert_eq!(results.len(), 2);
1265        assert!(results.iter().any(|p| p.name == "Mouse"));
1266        assert!(results.iter().any(|p| p.name == "Keyboard"));
1267    }
1268
1269    #[test]
1270    fn test_or_operator() {
1271        let products = create_test_products();
1272        
1273        let results: Vec<_> = products
1274            .lazy_query()
1275            .where_(Product::price(), |&p| p < 50.0)
1276            .or(Product::category(), |c| c == "Furniture")
1277            .collect();
1278        
1279        // Should find: Mouse (price < 50) and Desk Chair (Furniture)
1280        assert_eq!(results.len(), 2);
1281        assert!(results.iter().any(|p| p.name == "Mouse"));
1282        assert!(results.iter().any(|p| p.name == "Desk Chair"));
1283    }
1284
1285    #[test]
1286    fn test_complex_and_or_composition() {
1287        let products = create_test_products();
1288        
1289        // (price < 100 AND stock > 10) OR (category == "Furniture")
1290        let results: Vec<_> = products
1291            .lazy_query()
1292            .where_(Product::price(), |&p| p < 100.0)
1293            .and(Product::stock(), |&s| s > 10)
1294            .or(Product::category(), |c| c == "Furniture")
1295            .collect();
1296        
1297        // Should find: Mouse, Keyboard (from AND group), and Desk Chair (from OR group)
1298        assert_eq!(results.len(), 3);
1299        assert!(results.iter().any(|p| p.name == "Mouse"));
1300        assert!(results.iter().any(|p| p.name == "Keyboard"));
1301        assert!(results.iter().any(|p| p.name == "Desk Chair"));
1302    }
1303
1304    #[test]
1305    fn test_multiple_and_conditions() {
1306        let products = create_test_products();
1307        
1308        let results: Vec<_> = products
1309            .lazy_query()
1310            .where_(Product::price(), |&p| p < 200.0)
1311            .and(Product::stock(), |&s| s > 5)
1312            .and(Product::rating(), |&r| r > 4.5)
1313            .collect();
1314        
1315        // Should find: Keyboard (79.99, stock 30, rating 4.8) and Desk Chair (199.99, stock 8, rating 4.7)
1316        assert_eq!(results.len(), 2);
1317        assert!(results.iter().any(|p| p.name == "Keyboard"));
1318        assert!(results.iter().any(|p| p.name == "Desk Chair"));
1319    }
1320
1321    #[test]
1322    fn test_multiple_or_conditions() {
1323        let products = create_test_products();
1324        
1325        let results: Vec<_> = products
1326            .lazy_query()
1327            .where_(Product::price(), |&p| p > 500.0)
1328            .or(Product::category(), |c| c == "Furniture")
1329            .or(Product::rating(), |&r| r > 4.8)
1330            .collect();
1331        
1332        // Should find: Laptop (price > 500), Desk Chair (Furniture), Premium Laptop (rating > 4.8)
1333        assert_eq!(results.len(), 3);
1334        assert!(results.iter().any(|p| p.name == "Laptop"));
1335        assert!(results.iter().any(|p| p.name == "Desk Chair"));
1336        assert!(results.iter().any(|p| p.name == "Premium Laptop"));
1337    }
1338
1339    #[test]
1340    fn test_and_then_or_then_where() {
1341        let products = create_test_products();
1342        
1343        // where().and().or().where() - the second where should add to OR group
1344        let results: Vec<_> = products
1345            .lazy_query()
1346            .where_(Product::price(), |&p| p < 100.0)
1347            .and(Product::stock(), |&s| s > 10)
1348            .or(Product::category(), |c| c == "Furniture")
1349            .where_(Product::rating(), |&r| r > 4.0)  // This should add to OR group
1350            .collect();
1351        
1352        // Logic: (price < 100 AND stock > 10) OR (category == "Furniture" OR rating > 4.0)
1353        // Since rating > 4.0 is true for almost all, this should find most products
1354        assert!(results.len() >= 3);
1355    }
1356
1357    #[test]
1358    fn test_empty_results() {
1359        let products = create_test_products();
1360        
1361        // Impossible condition
1362        let results: Vec<_> = products
1363            .lazy_query()
1364            .where_(Product::price(), |&p| p < 0.0)
1365            .collect();
1366        
1367        assert_eq!(results.len(), 0);
1368    }
1369
1370    #[test]
1371    fn test_all_results() {
1372        let products = create_test_products();
1373        
1374        // Condition that matches all
1375        let results: Vec<_> = products
1376            .lazy_query()
1377            .where_(Product::price(), |&p| p > 0.0)
1378            .collect();
1379        
1380        assert_eq!(results.len(), products.len());
1381    }
1382
1383    #[test]
1384    fn test_or_with_no_previous_and() {
1385        let products = create_test_products();
1386        
1387        // Starting with OR (no previous AND group)
1388        let results: Vec<_> = products
1389            .lazy_query()
1390            .or(Product::category(), |c| c == "Furniture")
1391            .collect();
1392        
1393        // Should find Desk Chair
1394        assert_eq!(results.len(), 1);
1395        assert!(results[0].name == "Desk Chair");
1396    }
1397
1398    #[test]
1399    fn test_count_with_and_or() {
1400        let products = create_test_products();
1401        
1402        let count = products
1403            .lazy_query()
1404            .where_(Product::price(), |&p| p < 100.0)
1405            .and(Product::stock(), |&s| s > 10)
1406            .count();
1407        
1408        assert_eq!(count, 2);
1409    }
1410
1411    #[test]
1412    fn test_first_with_and_or() {
1413        let products = create_test_products();
1414        
1415        let first = products
1416            .lazy_query()
1417            .where_(Product::price(), |&p| p < 100.0)
1418            .and(Product::stock(), |&s| s > 10)
1419            .first();
1420        
1421        assert!(first.is_some());
1422        assert!(first.unwrap().price < 100.0);
1423        assert!(first.unwrap().stock > 10);
1424    }
1425
1426    #[test]
1427    fn test_any_with_and_or() {
1428        let products = create_test_products();
1429        
1430        let has_match = products
1431            .lazy_query()
1432            .where_(Product::price(), |&p| p < 50.0)
1433            .or(Product::category(), |c| c == "Furniture")
1434            .any();
1435        
1436        assert!(has_match);
1437    }
1438}