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