rust_queries_core/
lock_lazy.rs

1//! Lazy query support for locked data structures.
2//!
3//! Provides lazy evaluation with early termination for locked collections.
4
5use crate::locks::LockValue;
6use key_paths_core::KeyPaths;
7use std::marker::PhantomData;
8use std::collections::HashMap;
9use std::time::SystemTime;
10
11#[cfg(feature = "datetime")]
12use chrono::{DateTime, TimeZone};
13
14/// Lazy query for locked data with early termination.
15pub struct LockLazyQuery<'a, T: 'static, L, I>
16where
17    L: LockValue<T> + 'a,
18    I: Iterator<Item = &'a L>,
19{
20    iter: I,
21    _phantom: PhantomData<&'a T>,
22}
23
24impl<'a, T: 'static, L, I> LockLazyQuery<'a, T, L, I>
25where
26    L: LockValue<T> + 'a,
27    I: Iterator<Item = &'a L> + 'a,
28{
29    /// Create a new lazy query from an iterator of locks.
30    pub fn new(iter: I) -> Self {
31        Self {
32            iter,
33            _phantom: PhantomData,
34        }
35    }
36
37    /// Filter using a key-path predicate (lazy).
38    pub fn where_<F, P>(self, path: KeyPaths<T, F>, predicate: P) -> LockLazyQuery<'a, T, L, impl Iterator<Item = &'a L> + 'a>
39    where
40        F: 'static,
41        P: Fn(&F) -> bool + 'a,
42    {
43        LockLazyQuery {
44            iter: self.iter.filter(move |lock| {
45                lock.with_value(|item| {
46                    path.get(item).map_or(false, |val| predicate(val))
47                })
48                .unwrap_or(false)
49            }),
50            _phantom: PhantomData,
51        }
52    }
53
54    /// Map to a field value (lazy).
55    /// 
56    /// This allows you to select only specific fields from locked data without
57    /// cloning the entire object. Perfect for projecting data efficiently.
58    /// 
59    /// # Example
60    /// 
61    /// ```ignore
62    /// // Select only product names (not full objects)
63    /// let names: Vec<String> = products
64    ///     .lock_lazy_query()
65    ///     .where_(Product::price(), |&p| p > 100.0)
66    ///     .select_lazy(Product::name())
67    ///     .collect();
68    /// 
69    /// // Select only IDs  
70    /// let ids: Vec<u32> = products
71    ///     .lock_lazy_query()
72    ///     .where_(Product::stock(), |&s| s > 0)
73    ///     .select_lazy(Product::id())
74    ///     .take(10)
75    ///     .collect();
76    /// 
77    /// // Select prices and compute sum
78    /// let total: f64 = products
79    ///     .lock_lazy_query()
80    ///     .where_(Product::category(), |c| c == "Electronics")
81    ///     .select_lazy(Product::price())
82    ///     .sum();
83    /// ```
84    /// 
85    /// **Performance Note**: This is much more efficient than collecting full objects
86    /// and then extracting fields, as it only clones the specific field value.
87    pub fn select_lazy<F>(self, path: KeyPaths<T, F>) -> impl Iterator<Item = F> + 'a
88    where
89        F: Clone + 'static,
90    {
91        self.iter.filter_map(move |lock| {
92            lock.with_value(|item| path.get(item).cloned()).flatten()
93        })
94    }
95
96    /// Take first N items (lazy).
97    pub fn take_lazy(self, n: usize) -> impl Iterator<Item = T> + 'a
98    where
99        T: Clone,
100    {
101        self.iter
102            .filter_map(|lock| lock.with_value(|item| item.clone()))
103            .take(n)
104    }
105
106    /// Skip first N items (lazy).
107    pub fn skip_lazy(self, n: usize) -> LockLazyQuery<'a, T, L, impl Iterator<Item = &'a L> + 'a> {
108        LockLazyQuery {
109            iter: self.iter.skip(n),
110            _phantom: PhantomData,
111        }
112    }
113
114    /// Count matching items (terminal).
115    pub fn count(self) -> usize {
116        self.iter.count()
117    }
118
119    /// Get first matching item (terminal).
120    pub fn first(mut self) -> Option<T>
121    where
122        T: Clone,
123    {
124        self.iter
125            .find_map(|lock| lock.with_value(|item| item.clone()))
126    }
127
128    /// Check if any items match (terminal).
129    pub fn any(mut self) -> bool {
130        self.iter.next().is_some()
131    }
132
133    /// Collect into Vec (terminal).
134    pub fn collect(self) -> Vec<T>
135    where
136        T: Clone,
137    {
138        self.iter
139            .filter_map(|lock| lock.with_value(|item| item.clone()))
140            .collect()
141    }
142
143    /// Get all matching items (alias for collect, similar to LockQuery::all).
144    /// 
145    /// This provides a familiar API for users coming from LockQuery.
146    /// 
147    /// # Example
148    /// 
149    /// ```ignore
150    /// let all_items: Vec<Product> = products
151    ///     .lock_lazy_query()
152    ///     .where_(Product::price(), |&p| p > 100.0)
153    ///     .all();
154    /// ```
155    pub fn all(self) -> Vec<T>
156    where
157        T: Clone,
158    {
159        self.collect()
160    }
161
162    // ========================================================================
163    // AGGREGATION FUNCTIONS
164    // ========================================================================
165
166    /// Sum a numeric field (terminal).
167    /// 
168    /// Efficiently computes the sum without collecting all items into a Vec first.
169    /// 
170    /// # Example
171    /// 
172    /// ```ignore
173    /// // Sum all prices
174    /// let total_value: f64 = products
175    ///     .lock_lazy_query()
176    ///     .where_(Product::stock(), |&s| s > 0)
177    ///     .sum(Product::price());
178    /// 
179    /// // Sum stock quantities
180    /// let total_stock: u32 = products
181    ///     .lock_lazy_query()
182    ///     .where_(Product::category(), |c| c == "Electronics")
183    ///     .sum(Product::stock());
184    /// ```
185    pub fn sum<F>(self, path: KeyPaths<T, F>) -> F
186    where
187        F: Clone + std::ops::Add<Output = F> + Default + 'static,
188    {
189        self.iter
190            .filter_map(|lock| {
191                lock.with_value(|item| path.get(item).cloned()).flatten()
192            })
193            .fold(F::default(), |acc, val| acc + val)
194    }
195
196    /// Calculate average of f64 field (terminal).
197    /// 
198    /// Returns None if no items match.
199    /// 
200    /// # Example
201    /// 
202    /// ```ignore
203    /// let avg_price = products
204    ///     .lock_lazy_query()
205    ///     .where_(Product::stock(), |&s| s > 0)
206    ///     .avg(Product::price());
207    /// 
208    /// match avg_price {
209    ///     Some(avg) => println!("Average price: ${:.2}", avg),
210    ///     None => println!("No items found"),
211    /// }
212    /// ```
213    pub fn avg(self, path: KeyPaths<T, f64>) -> Option<f64> {
214        let values: Vec<f64> = self.iter
215            .filter_map(|lock| {
216                lock.with_value(|item| path.get(item).cloned()).flatten()
217            })
218            .collect();
219        
220        if values.is_empty() {
221            None
222        } else {
223            Some(values.iter().sum::<f64>() / values.len() as f64)
224        }
225    }
226
227    /// Find minimum value (terminal).
228    /// 
229    /// Returns None if no items match.
230    /// 
231    /// # Example
232    /// 
233    /// ```ignore
234    /// let min_stock = products
235    ///     .lock_lazy_query()
236    ///     .where_(Product::stock(), |&s| s > 0)
237    ///     .min(Product::stock());
238    /// 
239    /// println!("Minimum stock level: {:?}", min_stock);
240    /// ```
241    pub fn min<F>(self, path: KeyPaths<T, F>) -> Option<F>
242    where
243        F: Ord + Clone + 'static,
244    {
245        self.iter
246            .filter_map(|lock| {
247                lock.with_value(|item| path.get(item).cloned()).flatten()
248            })
249            .min()
250    }
251
252    /// Find maximum value (terminal).
253    /// 
254    /// Returns None if no items match.
255    /// 
256    /// # Example
257    /// 
258    /// ```ignore
259    /// let max_price = products
260    ///     .lock_lazy_query()
261    ///     .where_(Product::category(), |c| c == "Electronics")
262    ///     .max(Product::price());
263    /// 
264    /// println!("Most expensive: ${:.2}", max_price.unwrap_or(0.0));
265    /// ```
266    pub fn max<F>(self, path: KeyPaths<T, F>) -> Option<F>
267    where
268        F: Ord + Clone + 'static,
269    {
270        self.iter
271            .filter_map(|lock| {
272                lock.with_value(|item| path.get(item).cloned()).flatten()
273            })
274            .max()
275    }
276
277    /// Find minimum float value (terminal).
278    /// 
279    /// Handles f64 values correctly with partial ordering.
280    /// 
281    /// # Example
282    /// 
283    /// ```ignore
284    /// let cheapest = products
285    ///     .lock_lazy_query()
286    ///     .where_(Product::stock(), |&s| s > 0)
287    ///     .min_float(Product::price());
288    /// ```
289    pub fn min_float(self, path: KeyPaths<T, f64>) -> Option<f64> {
290        self.iter
291            .filter_map(|lock| {
292                lock.with_value(|item| path.get(item).cloned()).flatten()
293            })
294            .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
295    }
296
297    /// Find maximum float value (terminal).
298    /// 
299    /// Handles f64 values correctly with partial ordering.
300    /// 
301    /// # Example
302    /// 
303    /// ```ignore
304    /// let most_expensive = products
305    ///     .lock_lazy_query()
306    ///     .where_(Product::stock(), |&s| s > 0)
307    ///     .max_float(Product::price());
308    /// ```
309    pub fn max_float(self, path: KeyPaths<T, f64>) -> Option<f64> {
310        self.iter
311            .filter_map(|lock| {
312                lock.with_value(|item| path.get(item).cloned()).flatten()
313            })
314            .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
315    }
316
317    // ========================================================================
318    // SQL-LIKE FUNCTIONS
319    // ========================================================================
320
321    /// Check if any items exist matching the criteria (terminal).
322    /// 
323    /// Alias for `any()`, provides SQL-like EXISTS semantics.
324    /// Stops at the first match - very efficient!
325    /// 
326    /// # Example
327    /// 
328    /// ```ignore
329    /// let has_expensive = products
330    ///     .lock_lazy_query()
331    ///     .where_(Product::price(), |&p| p > 1000.0)
332    ///     .exists();
333    /// 
334    /// if has_expensive {
335    ///     println!("We have luxury items!");
336    /// }
337    /// 
338    /// // SQL equivalent: SELECT EXISTS(SELECT 1 FROM products WHERE price > 1000)
339    /// ```
340    pub fn exists(self) -> bool {
341        self.any()
342    }
343
344    /// Limit results to first N items (lazy).
345    /// 
346    /// Alias for creating a limited iterator. Use with `.collect()` or `.all()`.
347    /// 
348    /// # Example
349    /// 
350    /// ```ignore
351    /// let top_5: Vec<Product> = products
352    ///     .lock_lazy_query()
353    ///     .where_(Product::stock(), |&s| s > 10)
354    ///     .limit(5)
355    ///     .collect();
356    /// 
357    /// // SQL equivalent: SELECT * FROM products WHERE stock > 10 LIMIT 5
358    /// ```
359    pub fn limit(self, n: usize) -> impl Iterator<Item = T> + 'a
360    where
361        T: Clone,
362    {
363        self.iter
364            .filter_map(|lock| lock.with_value(|item| item.clone()))
365            .take(n)
366    }
367
368    /// Skip first N items (lazy).
369    /// 
370    /// Alias for `skip_lazy()` with better SQL-like naming.
371    /// 
372    /// # Example
373    /// 
374    /// ```ignore
375    /// // Get second page (skip 10, take 10)
376    /// let page_2 = products
377    ///     .lock_lazy_query()
378    ///     .skip(10)
379    ///     .limit(10)
380    ///     .collect();
381    /// 
382    /// // SQL equivalent: SELECT * FROM products LIMIT 10 OFFSET 10
383    /// ```
384    pub fn skip(self, n: usize) -> LockLazyQuery<'a, T, L, impl Iterator<Item = &'a L> + 'a> {
385        self.skip_lazy(n)
386    }
387
388    /// Get distinct values for a field (terminal).
389    /// 
390    /// Returns a Vec of unique field values. Uses HashSet internally.
391    /// 
392    /// # Example
393    /// 
394    /// ```ignore
395    /// let categories: Vec<String> = products
396    ///     .lock_lazy_query()
397    ///     .where_(Product::stock(), |&s| s > 0)
398    ///     .distinct(Product::category());
399    /// 
400    /// println!("Available categories: {:?}", categories);
401    /// 
402    /// // SQL equivalent: SELECT DISTINCT category FROM products WHERE stock > 0
403    /// ```
404    pub fn distinct<F>(self, path: KeyPaths<T, F>) -> Vec<F>
405    where
406        F: Eq + std::hash::Hash + Clone + 'static,
407    {
408        use std::collections::HashSet;
409        
410        let set: HashSet<F> = self.iter
411            .filter_map(|lock| {
412                lock.with_value(|item| path.get(item).cloned()).flatten()
413            })
414            .collect();
415        
416        set.into_iter().collect()
417    }
418
419    /// Get last matching item (terminal).
420    /// 
421    /// **Note**: This consumes the entire iterator to find the last item.
422    /// Less efficient than `first()` for lazy evaluation.
423    /// 
424    /// # Example
425    /// 
426    /// ```ignore
427    /// let last_product = products
428    ///     .lock_lazy_query()
429    ///     .where_(Product::stock(), |&s| s > 0)
430    ///     .last();
431    /// ```
432    pub fn last(self) -> Option<T>
433    where
434        T: Clone,
435    {
436        self.iter
437            .filter_map(|lock| lock.with_value(|item| item.clone()))
438            .last()
439    }
440
441    /// Find item at specific index (terminal).
442    /// 
443    /// Returns None if index is out of bounds.
444    /// 
445    /// # Example
446    /// 
447    /// ```ignore
448    /// let third_item = products
449    ///     .lock_lazy_query()
450    ///     .where_(Product::stock(), |&s| s > 0)
451    ///     .nth(2);  // 0-indexed, so this is the 3rd item
452    /// ```
453    pub fn nth(mut self, n: usize) -> Option<T>
454    where
455        T: Clone,
456    {
457        self.iter
458            .nth(n)
459            .and_then(|lock| lock.with_value(|item| item.clone()))
460    }
461
462    /// Check if all items match a predicate (terminal).
463    /// 
464    /// Returns true if all items match, false otherwise.
465    /// Short-circuits on first non-match.
466    /// 
467    /// # Example
468    /// 
469    /// ```ignore
470    /// let all_in_stock = products
471    ///     .lock_lazy_query()
472    ///     .where_(Product::category(), |c| c == "Electronics")
473    ///     .all_match(Product::stock(), |&s| s > 0);
474    /// 
475    /// if all_in_stock {
476    ///     println!("All electronics are in stock!");
477    /// }
478    /// ```
479    pub fn all_match<F, P>(mut self, path: KeyPaths<T, F>, predicate: P) -> bool
480    where
481        F: 'static,
482        P: Fn(&F) -> bool + 'a,
483    {
484        self.iter.all(|lock| {
485            lock.with_value(|item| {
486                path.get(item).map_or(false, |val| predicate(val))
487            })
488            .unwrap_or(false)
489        })
490    }
491
492    /// Find first item matching an additional predicate (terminal).
493    /// 
494    /// Like `first()` but with an extra condition.
495    /// 
496    /// # Example
497    /// 
498    /// ```ignore
499    /// let expensive_laptop = products
500    ///     .lock_lazy_query()
501    ///     .where_(Product::category(), |c| c == "Electronics")
502    ///     .find(Product::price(), |&p| p > 500.0);
503    /// ```
504    pub fn find<F, P>(mut self, path: KeyPaths<T, F>, predicate: P) -> Option<T>
505    where
506        F: 'static,
507        P: Fn(&F) -> bool + 'a,
508        T: Clone,
509    {
510        self.iter.find_map(|lock| {
511            lock.with_value(|item| {
512                if path.get(item).map_or(false, |val| predicate(val)) {
513                    Some(item.clone())
514                } else {
515                    None
516                }
517            })
518            .flatten()
519        })
520    }
521
522    /// Count items matching an additional condition (terminal).
523    /// 
524    /// Like `count()` but with a field-specific predicate.
525    /// 
526    /// # Example
527    /// 
528    /// ```ignore
529    /// let expensive_count = products
530    ///     .lock_lazy_query()
531    ///     .where_(Product::category(), |c| c == "Electronics")
532    ///     .count_where(Product::price(), |&p| p > 500.0);
533    /// ```
534    pub fn count_where<F, P>(self, path: KeyPaths<T, F>, predicate: P) -> usize
535    where
536        F: 'static,
537        P: Fn(&F) -> bool + 'a,
538    {
539        self.iter.filter(|lock| {
540            lock.with_value(|item| {
541                path.get(item).map_or(false, |val| predicate(val))
542            })
543            .unwrap_or(false)
544        }).count()
545    }
546
547    // ========================================================================
548    // DATETIME OPERATIONS - SystemTime
549    // ========================================================================
550
551    /// Filter by SystemTime being after a reference time.
552    ///
553    /// # Arguments
554    ///
555    /// * `path` - The key-path to the SystemTime field
556    /// * `reference` - The reference time to compare against
557    ///
558    /// # Example
559    ///
560    /// ```ignore
561    /// let recent = events
562    ///     .lock_lazy_query()
563    ///     .where_after_systemtime(Event::timestamp(), cutoff_time);
564    /// ```
565    pub fn where_after_systemtime(self, path: KeyPaths<T, SystemTime>, reference: SystemTime) -> LockLazyQuery<'a, T, L, impl Iterator<Item = &'a L> + 'a> {
566        self.where_(path, move |time| time > &reference)
567    }
568
569    /// Filter by SystemTime being before a reference time.
570    ///
571    /// # Arguments
572    ///
573    /// * `path` - The key-path to the SystemTime field
574    /// * `reference` - The reference time to compare against
575    ///
576    /// # Example
577    ///
578    /// ```ignore
579    /// let old = events
580    ///     .lock_lazy_query()
581    ///     .where_before_systemtime(Event::timestamp(), cutoff_time);
582    /// ```
583    pub fn where_before_systemtime(self, path: KeyPaths<T, SystemTime>, reference: SystemTime) -> LockLazyQuery<'a, T, L, impl Iterator<Item = &'a L> + 'a> {
584        self.where_(path, move |time| time < &reference)
585    }
586
587    /// Filter by SystemTime being between two times (inclusive).
588    ///
589    /// # Arguments
590    ///
591    /// * `path` - The key-path to the SystemTime field
592    /// * `start` - The start time
593    /// * `end` - The end time
594    ///
595    /// # Example
596    ///
597    /// ```ignore
598    /// let range = events
599    ///     .lock_lazy_query()
600    ///     .where_between_systemtime(Event::timestamp(), start, end);
601    /// ```
602    pub fn where_between_systemtime(
603        self,
604        path: KeyPaths<T, SystemTime>,
605        start: SystemTime,
606        end: SystemTime,
607    ) -> LockLazyQuery<'a, T, L, impl Iterator<Item = &'a L> + 'a> {
608        self.where_(path, move |time| time >= &start && time <= &end)
609    }
610
611    // ========================================================================
612    // ORDERING OPERATIONS (require T: Clone)
613    // ========================================================================
614
615    /// Orders results by a field in ascending order (terminal).
616    /// 
617    /// **Note**: This method requires `T: Clone` as it creates owned sorted copies.
618    /// This is a terminal operation that collects and sorts all matching items.
619    ///
620    /// # Arguments
621    ///
622    /// * `path` - The key-path to the field to order by
623    ///
624    /// # Example
625    ///
626    /// ```ignore
627    /// let sorted = products
628    ///     .lock_lazy_query()
629    ///     .where_(Product::stock(), |&s| s > 0)
630    ///     .order_by(Product::name());
631    /// ```
632    pub fn order_by<F>(self, path: KeyPaths<T, F>) -> Vec<T>
633    where
634        F: Ord + Clone + 'static,
635        T: Clone,
636    {
637        let mut results: Vec<T> = self.iter
638            .filter_map(|lock| lock.with_value(|item| item.clone()))
639            .collect();
640
641        results.sort_by_key(|item| path.get(item).cloned());
642        results
643    }
644
645    /// Orders results by a field in descending order (terminal).
646    /// 
647    /// **Note**: This method requires `T: Clone` as it creates owned sorted copies.
648    /// This is a terminal operation that collects and sorts all matching items.
649    ///
650    /// # Arguments
651    ///
652    /// * `path` - The key-path to the field to order by
653    ///
654    /// # Example
655    ///
656    /// ```ignore
657    /// let sorted = products
658    ///     .lock_lazy_query()
659    ///     .where_(Product::stock(), |&s| s > 0)
660    ///     .order_by_desc(Product::stock());
661    /// ```
662    pub fn order_by_desc<F>(self, path: KeyPaths<T, F>) -> Vec<T>
663    where
664        F: Ord + Clone + 'static,
665        T: Clone,
666    {
667        let mut results: Vec<T> = self.iter
668            .filter_map(|lock| lock.with_value(|item| item.clone()))
669            .collect();
670
671        results.sort_by(|a, b| {
672            let a_val = path.get(a).cloned();
673            let b_val = path.get(b).cloned();
674            b_val.cmp(&a_val)
675        });
676        results
677    }
678
679    /// Orders results by a float field in ascending order (terminal).
680    /// 
681    /// **Note**: This method requires `T: Clone` as it creates owned sorted copies.
682    /// This is a terminal operation that collects and sorts all matching items.
683    ///
684    /// # Arguments
685    ///
686    /// * `path` - The key-path to the f64 field to order by
687    ///
688    /// # Example
689    ///
690    /// ```ignore
691    /// let sorted = products
692    ///     .lock_lazy_query()
693    ///     .where_(Product::stock(), |&s| s > 0)
694    ///     .order_by_float(Product::price());
695    /// ```
696    pub fn order_by_float(self, path: KeyPaths<T, f64>) -> Vec<T>
697    where
698        T: Clone,
699    {
700        let mut results: Vec<T> = self.iter
701            .filter_map(|lock| lock.with_value(|item| item.clone()))
702            .collect();
703
704        results.sort_by(|a, b| {
705            let a_val = path.get(a).cloned().unwrap_or(0.0);
706            let b_val = path.get(b).cloned().unwrap_or(0.0);
707            a_val.partial_cmp(&b_val).unwrap_or(std::cmp::Ordering::Equal)
708        });
709        results
710    }
711
712    /// Orders results by a float field in descending order (terminal).
713    /// 
714    /// **Note**: This method requires `T: Clone` as it creates owned sorted copies.
715    /// This is a terminal operation that collects and sorts all matching items.
716    ///
717    /// # Arguments
718    ///
719    /// * `path` - The key-path to the f64 field to order by
720    ///
721    /// # Example
722    ///
723    /// ```ignore
724    /// let sorted = products
725    ///     .lock_lazy_query()
726    ///     .where_(Product::stock(), |&s| s > 0)
727    ///     .order_by_float_desc(Product::rating());
728    /// ```
729    pub fn order_by_float_desc(self, path: KeyPaths<T, f64>) -> Vec<T>
730    where
731        T: Clone,
732    {
733        let mut results: Vec<T> = self.iter
734            .filter_map(|lock| lock.with_value(|item| item.clone()))
735            .collect();
736
737        results.sort_by(|a, b| {
738            let a_val = path.get(a).cloned().unwrap_or(0.0);
739            let b_val = path.get(b).cloned().unwrap_or(0.0);
740            b_val.partial_cmp(&a_val).unwrap_or(std::cmp::Ordering::Equal)
741        });
742        results
743    }
744
745    // ========================================================================
746    // GROUPING OPERATIONS (require T: Clone)
747    // ========================================================================
748
749    /// Groups results by a field value (terminal).
750    /// 
751    /// **Note**: This method requires `T: Clone` as it creates owned copies in groups.
752    /// This is a terminal operation that collects all matching items into groups.
753    ///
754    /// # Arguments
755    ///
756    /// * `path` - The key-path to the field to group by
757    ///
758    /// # Example
759    ///
760    /// ```ignore
761    /// let by_category = products
762    ///     .lock_lazy_query()
763    ///     .where_(Product::stock(), |&s| s > 0)
764    ///     .group_by(Product::category());
765    /// 
766    /// for (category, products) in by_category {
767    ///     println!("{}: {} products", category, products.len());
768    /// }
769    /// 
770    /// // SQL equivalent: SELECT * FROM products WHERE stock > 0 GROUP BY category
771    /// ```
772    pub fn group_by<F>(self, path: KeyPaths<T, F>) -> HashMap<F, Vec<T>>
773    where
774        F: Eq + std::hash::Hash + Clone + 'static,
775        T: Clone,
776    {
777        let mut groups: HashMap<F, Vec<T>> = HashMap::new();
778
779        for lock in self.iter {
780            if let Some(item) = lock.with_value(|item| item.clone()) {
781                if let Some(key) = path.get(&item).cloned() {
782                    groups.entry(key).or_insert_with(Vec::new).push(item);
783                }
784            }
785        }
786
787        groups
788    }
789}
790
791// ========================================================================
792// DATETIME OPERATIONS - Chrono (only available with datetime feature)
793// ========================================================================
794
795#[cfg(feature = "datetime")]
796impl<'a, T: 'static, L, I> LockLazyQuery<'a, T, L, I>
797where
798    L: LockValue<T> + 'a,
799    I: Iterator<Item = &'a L> + 'a,
800{
801    /// Filter by DateTime being after a reference time.
802    ///
803    /// # Arguments
804    ///
805    /// * `path` - The key-path to the DateTime field
806    /// * `reference` - The reference time to compare against
807    ///
808    /// # Example
809    ///
810    /// ```ignore
811    /// let recent = events
812    ///     .lock_lazy_query()
813    ///     .where_after(Event::timestamp(), cutoff_time);
814    /// ```
815    pub fn where_after<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, reference: DateTime<Tz>) -> LockLazyQuery<'a, T, L, impl Iterator<Item = &'a L> + 'a>
816    where
817        Tz: TimeZone + 'static,
818        Tz::Offset: std::fmt::Display,
819    {
820        self.where_(path, move |time| time > &reference)
821    }
822
823    /// Filter by DateTime being before a reference time.
824    ///
825    /// # Arguments
826    ///
827    /// * `path` - The key-path to the DateTime field
828    /// * `reference` - The reference time to compare against
829    ///
830    /// # Example
831    ///
832    /// ```ignore
833    /// let old = events
834    ///     .lock_lazy_query()
835    ///     .where_before(Event::timestamp(), cutoff_time);
836    /// ```
837    pub fn where_before<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, reference: DateTime<Tz>) -> LockLazyQuery<'a, T, L, impl Iterator<Item = &'a L> + 'a>
838    where
839        Tz: TimeZone + 'static,
840        Tz::Offset: std::fmt::Display,
841    {
842        self.where_(path, move |time| time < &reference)
843    }
844
845    /// Filter by DateTime being between two times (inclusive).
846    ///
847    /// # Arguments
848    ///
849    /// * `path` - The key-path to the DateTime field
850    /// * `start` - The start time
851    /// * `end` - The end time
852    ///
853    /// # Example
854    ///
855    /// ```ignore
856    /// let range = events
857    ///     .lock_lazy_query()
858    ///     .where_between(Event::timestamp(), start, end);
859    /// ```
860    pub fn where_between<Tz>(
861        self,
862        path: KeyPaths<T, DateTime<Tz>>,
863        start: DateTime<Tz>,
864        end: DateTime<Tz>,
865    ) -> LockLazyQuery<'a, T, L, impl Iterator<Item = &'a L> + 'a>
866    where
867        Tz: TimeZone + 'static,
868        Tz::Offset: std::fmt::Display,
869    {
870        self.where_(path, move |time| time >= &start && time <= &end)
871    }
872
873    /// Filter by DateTime being today.
874    ///
875    /// # Arguments
876    ///
877    /// * `path` - The key-path to the DateTime field
878    /// * `now` - The current DateTime to compare against
879    ///
880    /// # Example
881    ///
882    /// ```ignore
883    /// let today = events
884    ///     .lock_lazy_query()
885    ///     .where_today(Event::timestamp(), Utc::now());
886    /// ```
887    pub fn where_today<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, now: DateTime<Tz>) -> LockLazyQuery<'a, T, L, impl Iterator<Item = &'a L> + 'a>
888    where
889        Tz: TimeZone + 'static,
890        Tz::Offset: std::fmt::Display,
891    {
892        self.where_(path, move |time| {
893            time.date_naive() == now.date_naive()
894        })
895    }
896
897    /// Filter by DateTime year.
898    ///
899    /// # Arguments
900    ///
901    /// * `path` - The key-path to the DateTime field
902    /// * `year` - The year to filter by
903    ///
904    /// # Example
905    ///
906    /// ```ignore
907    /// let this_year = events
908    ///     .lock_lazy_query()
909    ///     .where_year(Event::timestamp(), 2024);
910    /// ```
911    pub fn where_year<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, year: i32) -> LockLazyQuery<'a, T, L, impl Iterator<Item = &'a L> + 'a>
912    where
913        Tz: TimeZone + 'static,
914        Tz::Offset: std::fmt::Display,
915    {
916        use chrono::Datelike;
917        self.where_(path, move |time| time.year() == year)
918    }
919
920    /// Filter by DateTime month.
921    ///
922    /// # Arguments
923    ///
924    /// * `path` - The key-path to the DateTime field
925    /// * `month` - The month to filter by (1-12)
926    ///
927    /// # Example
928    ///
929    /// ```ignore
930    /// let december = events
931    ///     .lock_lazy_query()
932    ///     .where_month(Event::timestamp(), 12);
933    /// ```
934    pub fn where_month<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, month: u32) -> LockLazyQuery<'a, T, L, impl Iterator<Item = &'a L> + 'a>
935    where
936        Tz: TimeZone + 'static,
937        Tz::Offset: std::fmt::Display,
938    {
939        use chrono::Datelike;
940        self.where_(path, move |time| time.month() == month)
941    }
942
943    /// Filter by DateTime day.
944    ///
945    /// # Arguments
946    ///
947    /// * `path` - The key-path to the DateTime field
948    /// * `day` - The day to filter by (1-31)
949    ///
950    /// # Example
951    ///
952    /// ```ignore
953    /// let first = events
954    ///     .lock_lazy_query()
955    ///     .where_day(Event::timestamp(), 1);
956    /// ```
957    pub fn where_day<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, day: u32) -> LockLazyQuery<'a, T, L, impl Iterator<Item = &'a L> + 'a>
958    where
959        Tz: TimeZone + 'static,
960        Tz::Offset: std::fmt::Display,
961    {
962        use chrono::Datelike;
963        self.where_(path, move |time| time.day() == day)
964    }
965
966    /// Filter by weekend dates (Saturday and Sunday).
967    ///
968    /// # Arguments
969    ///
970    /// * `path` - The key-path to the DateTime field
971    ///
972    /// # Example
973    ///
974    /// ```ignore
975    /// let weekend_events = events
976    ///     .lock_lazy_query()
977    ///     .where_weekend(Event::timestamp());
978    /// ```
979    pub fn where_weekend<Tz>(self, path: KeyPaths<T, DateTime<Tz>>) -> LockLazyQuery<'a, T, L, impl Iterator<Item = &'a L> + 'a>
980    where
981        Tz: TimeZone + 'static,
982        Tz::Offset: std::fmt::Display,
983    {
984        use chrono::Datelike;
985        self.where_(path, |time| {
986            let weekday = time.weekday().num_days_from_monday();
987            weekday >= 5
988        })
989    }
990
991    /// Filter by weekday dates (Monday through Friday).
992    ///
993    /// # Arguments
994    ///
995    /// * `path` - The key-path to the DateTime field
996    ///
997    /// # Example
998    ///
999    /// ```ignore
1000    /// let weekday_events = events
1001    ///     .lock_lazy_query()
1002    ///     .where_weekday(Event::timestamp());
1003    /// ```
1004    pub fn where_weekday<Tz>(self, path: KeyPaths<T, DateTime<Tz>>) -> LockLazyQuery<'a, T, L, impl Iterator<Item = &'a L> + 'a>
1005    where
1006        Tz: TimeZone + 'static,
1007        Tz::Offset: std::fmt::Display,
1008    {
1009        use chrono::Datelike;
1010        self.where_(path, |time| {
1011            let weekday = time.weekday().num_days_from_monday();
1012            weekday < 5
1013        })
1014    }
1015
1016    /// Filter by business hours (9 AM - 5 PM).
1017    ///
1018    /// # Arguments
1019    ///
1020    /// * `path` - The key-path to the DateTime field
1021    ///
1022    /// # Example
1023    ///
1024    /// ```ignore
1025    /// let business_hours = events
1026    ///     .lock_lazy_query()
1027    ///     .where_business_hours(Event::timestamp());
1028    /// ```
1029    pub fn where_business_hours<Tz>(self, path: KeyPaths<T, DateTime<Tz>>) -> LockLazyQuery<'a, T, L, impl Iterator<Item = &'a L> + 'a>
1030    where
1031        Tz: TimeZone + 'static,
1032        Tz::Offset: std::fmt::Display,
1033    {
1034        use chrono::Timelike;
1035        self.where_(path, |time| {
1036            let hour = time.hour();
1037            hour >= 9 && hour < 17
1038        })
1039    }
1040}
1041
1042// ========================================================================
1043// i64 DATETIME AGGREGATORS (Unix timestamps in milliseconds)
1044// ========================================================================
1045
1046impl<'a, T: 'static, L, I> LockLazyQuery<'a, T, L, I>
1047where
1048    L: LockValue<T> + 'a,
1049    I: Iterator<Item = &'a L> + 'a,
1050{
1051    /// Finds minimum i64 timestamp value (terminal operation).
1052    ///
1053    /// # Example
1054    ///
1055    /// ```ignore
1056    /// let earliest = events
1057    ///     .lock_lazy_query()
1058    ///     .min_timestamp(Event::created_at());
1059    /// ```
1060    pub fn min_timestamp(self, path: KeyPaths<T, i64>) -> Option<i64> {
1061        self.iter
1062            .filter_map(|lock| {
1063                lock.with_value(|item| path.get(item).cloned()).flatten()
1064            })
1065            .min()
1066    }
1067
1068    /// Finds maximum i64 timestamp value (terminal operation).
1069    ///
1070    /// # Example
1071    ///
1072    /// ```ignore
1073    /// let latest = events
1074    ///     .lock_lazy_query()
1075    ///     .max_timestamp(Event::created_at());
1076    /// ```
1077    pub fn max_timestamp(self, path: KeyPaths<T, i64>) -> Option<i64> {
1078        self.iter
1079            .filter_map(|lock| {
1080                lock.with_value(|item| path.get(item).cloned()).flatten()
1081            })
1082            .max()
1083    }
1084
1085    /// Computes average of i64 timestamp values (terminal operation).
1086    ///
1087    /// # Example
1088    ///
1089    /// ```ignore
1090    /// let avg = events
1091    ///     .lock_lazy_query()
1092    ///     .avg_timestamp(Event::created_at());
1093    /// ```
1094    pub fn avg_timestamp(self, path: KeyPaths<T, i64>) -> Option<i64> {
1095        let items: Vec<i64> = self.iter
1096            .filter_map(|lock| {
1097                lock.with_value(|item| path.get(item).cloned()).flatten()
1098            })
1099            .collect();
1100
1101        if items.is_empty() {
1102            None
1103        } else {
1104            Some(items.iter().sum::<i64>() / items.len() as i64)
1105        }
1106    }
1107
1108    /// Computes sum of i64 timestamp values (terminal operation).
1109    ///
1110    /// # Example
1111    ///
1112    /// ```ignore
1113    /// let total = events
1114    ///     .lock_lazy_query()
1115    ///     .sum_timestamp(Event::created_at());
1116    /// ```
1117    pub fn sum_timestamp(self, path: KeyPaths<T, i64>) -> i64 {
1118        self.iter
1119            .filter_map(|lock| {
1120                lock.with_value(|item| path.get(item).cloned()).flatten()
1121            })
1122            .sum()
1123    }
1124
1125    /// Counts i64 timestamp values (terminal operation).
1126    ///
1127    /// # Example
1128    ///
1129    /// ```ignore
1130    /// let count = events
1131    ///     .lock_lazy_query()
1132    ///     .count_timestamp(Event::created_at());
1133    /// ```
1134    pub fn count_timestamp(self, path: KeyPaths<T, i64>) -> usize {
1135        self.iter
1136            .filter(|lock| {
1137                lock.with_value(|item| path.get(item).is_some()).unwrap_or(false)
1138            })
1139            .count()
1140    }
1141
1142    /// Filter by i64 timestamp being after a reference time (lazy).
1143    ///
1144    /// # Arguments
1145    ///
1146    /// * `path` - The key-path to the i64 timestamp field
1147    /// * `reference` - The reference timestamp to compare against
1148    ///
1149    /// # Example
1150    ///
1151    /// ```ignore
1152    /// let recent = events
1153    ///     .lock_lazy_query()
1154    ///     .where_after_timestamp(Event::created_at(), cutoff_time);
1155    /// ```
1156    pub fn where_after_timestamp(self, path: KeyPaths<T, i64>, reference: i64) -> LockLazyQuery<'a, T, L, impl Iterator<Item = &'a L> + 'a> {
1157        self.where_(path, move |timestamp| timestamp > &reference)
1158    }
1159
1160    /// Filter by i64 timestamp being before a reference time (lazy).
1161    ///
1162    /// # Arguments
1163    ///
1164    /// * `path` - The key-path to the i64 timestamp field
1165    /// * `reference` - The reference timestamp to compare against
1166    ///
1167    /// # Example
1168    ///
1169    /// ```ignore
1170    /// let old = events
1171    ///     .lock_lazy_query()
1172    ///     .where_before_timestamp(Event::created_at(), cutoff_time);
1173    /// ```
1174    pub fn where_before_timestamp(self, path: KeyPaths<T, i64>, reference: i64) -> LockLazyQuery<'a, T, L, impl Iterator<Item = &'a L> + 'a> {
1175        self.where_(path, move |timestamp| timestamp < &reference)
1176    }
1177
1178    /// Filter by i64 timestamp being between two times (inclusive, lazy).
1179    ///
1180    /// # Arguments
1181    ///
1182    /// * `path` - The key-path to the i64 timestamp field
1183    /// * `start` - The start timestamp
1184    /// * `end` - The end timestamp
1185    ///
1186    /// # Example
1187    ///
1188    /// ```ignore
1189    /// let range = events
1190    ///     .lock_lazy_query()
1191    ///     .where_between_timestamp(Event::created_at(), start, end);
1192    /// ```
1193    pub fn where_between_timestamp(
1194        self,
1195        path: KeyPaths<T, i64>,
1196        start: i64,
1197        end: i64,
1198    ) -> LockLazyQuery<'a, T, L, impl Iterator<Item = &'a L> + 'a> {
1199        self.where_(path, move |timestamp| timestamp >= &start && timestamp <= &end)
1200    }
1201
1202    /// Filter by i64 timestamp being within the last N days (lazy).
1203    ///
1204    /// # Arguments
1205    ///
1206    /// * `path` - The key-path to the i64 timestamp field
1207    /// * `days` - Number of days to look back
1208    ///
1209    /// # Example
1210    ///
1211    /// ```ignore
1212    /// let recent = events
1213    ///     .lock_lazy_query()
1214    ///     .where_last_days_timestamp(Event::created_at(), 30);
1215    /// ```
1216    pub fn where_last_days_timestamp(self, path: KeyPaths<T, i64>, days: i64) -> LockLazyQuery<'a, T, L, impl Iterator<Item = &'a L> + 'a> {
1217        let now = chrono::Utc::now().timestamp_millis();
1218        let cutoff = now - (days * 24 * 60 * 60 * 1000); // Convert days to milliseconds
1219        self.where_after_timestamp(path, cutoff)
1220    }
1221
1222    /// Filter by i64 timestamp being within the next N days (lazy).
1223    ///
1224    /// # Arguments
1225    ///
1226    /// * `path` - The key-path to the i64 timestamp field
1227    /// * `days` - Number of days to look forward
1228    ///
1229    /// # Example
1230    ///
1231    /// ```ignore
1232    /// let upcoming = events
1233    ///     .lock_lazy_query()
1234    ///     .where_next_days_timestamp(Event::scheduled_at(), 7);
1235    /// ```
1236    pub fn where_next_days_timestamp(self, path: KeyPaths<T, i64>, days: i64) -> LockLazyQuery<'a, T, L, impl Iterator<Item = &'a L> + 'a> {
1237        let now = chrono::Utc::now().timestamp_millis();
1238        let cutoff = now + (days * 24 * 60 * 60 * 1000); // Convert days to milliseconds
1239        self.where_before_timestamp(path, cutoff)
1240    }
1241
1242    /// Filter by i64 timestamp being within the last N hours (lazy).
1243    ///
1244    /// # Arguments
1245    ///
1246    /// * `path` - The key-path to the i64 timestamp field
1247    /// * `hours` - Number of hours to look back
1248    ///
1249    /// # Example
1250    ///
1251    /// ```ignore
1252    /// let recent = events
1253    ///     .lock_lazy_query()
1254    ///     .where_last_hours_timestamp(Event::created_at(), 24);
1255    /// ```
1256    pub fn where_last_hours_timestamp(self, path: KeyPaths<T, i64>, hours: i64) -> LockLazyQuery<'a, T, L, impl Iterator<Item = &'a L> + 'a> {
1257        let now = chrono::Utc::now().timestamp_millis();
1258        let cutoff = now - (hours * 60 * 60 * 1000); // Convert hours to milliseconds
1259        self.where_after_timestamp(path, cutoff)
1260    }
1261
1262    /// Filter by i64 timestamp being within the next N hours (lazy).
1263    ///
1264    /// # Arguments
1265    ///
1266    /// * `path` - The key-path to the i64 timestamp field
1267    /// * `hours` - Number of hours to look forward
1268    ///
1269    /// # Example
1270    ///
1271    /// ```ignore
1272    /// let upcoming = events
1273    ///     .lock_lazy_query()
1274    ///     .where_next_hours_timestamp(Event::scheduled_at(), 2);
1275    /// ```
1276    pub fn where_next_hours_timestamp(self, path: KeyPaths<T, i64>, hours: i64) -> LockLazyQuery<'a, T, L, impl Iterator<Item = &'a L> + 'a> {
1277        let now = chrono::Utc::now().timestamp_millis();
1278        let cutoff = now + (hours * 60 * 60 * 1000); // Convert hours to milliseconds
1279        self.where_before_timestamp(path, cutoff)
1280    }
1281
1282    /// Filter by i64 timestamp being within the last N minutes (lazy).
1283    ///
1284    /// # Arguments
1285    ///
1286    /// * `path` - The key-path to the i64 timestamp field
1287    /// * `minutes` - Number of minutes to look back
1288    ///
1289    /// # Example
1290    ///
1291    /// ```ignore
1292    /// let recent = events
1293    ///     .lock_lazy_query()
1294    ///     .where_last_minutes_timestamp(Event::created_at(), 60);
1295    /// ```
1296    pub fn where_last_minutes_timestamp(self, path: KeyPaths<T, i64>, minutes: i64) -> LockLazyQuery<'a, T, L, impl Iterator<Item = &'a L> + 'a> {
1297        let now = chrono::Utc::now().timestamp_millis();
1298        let cutoff = now - (minutes * 60 * 1000); // Convert minutes to milliseconds
1299        self.where_after_timestamp(path, cutoff)
1300    }
1301
1302    /// Filter by i64 timestamp being within the next N minutes (lazy).
1303    ///
1304    /// # Arguments
1305    ///
1306    /// * `path` - The key-path to the i64 timestamp field
1307    /// * `minutes` - Number of minutes to look forward
1308    ///
1309    /// # Example
1310    ///
1311    /// ```ignore
1312    /// let upcoming = events
1313    ///     .lock_lazy_query()
1314    ///     .where_next_minutes_timestamp(Event::scheduled_at(), 30);
1315    /// ```
1316    pub fn where_next_minutes_timestamp(self, path: KeyPaths<T, i64>, minutes: i64) -> LockLazyQuery<'a, T, L, impl Iterator<Item = &'a L> + 'a> {
1317        let now = chrono::Utc::now().timestamp_millis();
1318        let cutoff = now + (minutes * 60 * 1000); // Convert minutes to milliseconds
1319        self.where_before_timestamp(path, cutoff)
1320    }
1321}
1322
1323