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}