rust_queries_core/lazy.rs
1//! Lazy query implementation using iterators.
2//!
3//! This module provides lazy evaluation of queries, deferring execution
4//! until results are actually consumed.
5
6use key_paths_core::KeyPaths;
7use std::marker::PhantomData;
8use std::time::SystemTime;
9
10#[cfg(feature = "datetime")]
11use chrono::{DateTime, TimeZone};
12
13/// A lazy query builder that uses iterators for deferred execution.
14///
15/// Unlike the standard `Query`, `LazyQuery` doesn't execute until you call
16/// a terminal operation like `.collect()`, `.count()`, or `.first()`.
17///
18/// # Benefits
19///
20/// - **Deferred execution**: No work until results needed
21/// - **Iterator fusion**: Rust optimizes chained operations
22/// - **Early termination**: `.take()` stops as soon as enough items found
23/// - **Composable**: Build complex queries by composition
24///
25/// # Example
26///
27/// ```ignore
28/// // Nothing executes yet
29/// let query = LazyQuery::new(&products)
30/// .where_(Product::price_r(), |&p| p < 100.0)
31/// .where_(Product::stock_r(), |&s| s > 0);
32///
33/// // Execution happens here
34/// let results: Vec<_> = query.collect();
35/// ```
36pub struct LazyQuery<'a, T: 'static, I>
37where
38 I: Iterator<Item = &'a T>,
39{
40 iter: I,
41 _phantom: PhantomData<&'a T>,
42}
43
44impl<'a, T: 'static> LazyQuery<'a, T, std::slice::Iter<'a, T>> {
45 /// Creates a new lazy query from a slice.
46 ///
47 /// # Example
48 ///
49 /// ```ignore
50 /// let query = LazyQuery::new(&products);
51 /// ```
52 pub fn new(data: &'a [T]) -> Self {
53 Self {
54 iter: data.iter(),
55 _phantom: PhantomData,
56 }
57 }
58}
59
60impl<'a, T: 'static, I> LazyQuery<'a, T, I>
61where
62 I: Iterator<Item = &'a T>,
63{
64 /// Creates a new lazy query from an iterator.
65 ///
66 /// This is useful for creating LazyQuery instances from custom iterators
67 /// or for implementing extension traits.
68 ///
69 /// # Example
70 ///
71 /// ```ignore
72 /// let iter = vec![1, 2, 3].iter();
73 /// let query = LazyQuery::from_iter(iter);
74 /// ```
75 pub fn from_iter(iter: I) -> Self {
76 Self {
77 iter,
78 _phantom: PhantomData,
79 }
80 }
81}
82
83impl<'a, T: 'static, I> LazyQuery<'a, T, I>
84where
85 I: Iterator<Item = &'a T> + 'a,
86{
87 /// Adds a filter predicate (lazy - not executed yet).
88 ///
89 /// # Example
90 ///
91 /// ```ignore
92 /// let query = LazyQuery::new(&products)
93 /// .where_(Product::price_r(), |&p| p < 100.0);
94 /// ```
95 pub fn where_<F, P>(self, path: KeyPaths<T, F>, predicate: P) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
96 where
97 F: 'static,
98 P: Fn(&F) -> bool + 'a,
99 {
100 LazyQuery {
101 iter: self.iter.filter(move |item| {
102 path.get(item).map_or(false, |val| predicate(val))
103 }),
104 _phantom: PhantomData,
105 }
106 }
107
108 /// Maps each item through a transformation (lazy).
109 ///
110 /// # Example
111 ///
112 /// ```ignore
113 /// let prices = LazyQuery::new(&products)
114 /// .map_items(|p| p.price)
115 /// .collect::<Vec<_>>();
116 /// ```
117 pub fn map_items<F, O>(self, f: F) -> impl Iterator<Item = O> + 'a
118 where
119 F: Fn(&'a T) -> O + 'a,
120 I: 'a,
121 {
122 self.iter.map(f)
123 }
124
125 /// Selects/projects a field value (lazy).
126 ///
127 /// Returns iterator over cloned field values.
128 ///
129 /// # Example
130 ///
131 /// ```ignore
132 /// let names: Vec<String> = LazyQuery::new(&products)
133 /// .select_lazy(Product::name_r())
134 /// .collect();
135 /// ```
136 pub fn select_lazy<F>(self, path: KeyPaths<T, F>) -> impl Iterator<Item = F> + 'a
137 where
138 F: Clone + 'static,
139 I: 'a,
140 {
141 self.iter.filter_map(move |item| path.get(item).cloned())
142 }
143
144 /// Takes at most `n` items (lazy).
145 ///
146 /// # Example
147 ///
148 /// ```ignore
149 /// let first_10: Vec<_> = LazyQuery::new(&products)
150 /// .take_lazy(10)
151 /// .collect();
152 /// ```
153 pub fn take_lazy(self, n: usize) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
154 where
155 I: 'a,
156 {
157 LazyQuery {
158 iter: self.iter.take(n),
159 _phantom: PhantomData,
160 }
161 }
162
163 /// Skips `n` items (lazy).
164 ///
165 /// # Example
166 ///
167 /// ```ignore
168 /// let page_2: Vec<_> = LazyQuery::new(&products)
169 /// .skip_lazy(10)
170 /// .take_lazy(10)
171 /// .collect();
172 /// ```
173 pub fn skip_lazy(self, n: usize) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
174 where
175 I: 'a,
176 {
177 LazyQuery {
178 iter: self.iter.skip(n),
179 _phantom: PhantomData,
180 }
181 }
182
183 /// Collects all items into a vector (terminal operation - executes query).
184 ///
185 /// # Example
186 ///
187 /// ```ignore
188 /// let results: Vec<&Product> = query.collect();
189 /// ```
190 pub fn collect(self) -> Vec<&'a T> {
191 self.iter.collect()
192 }
193
194 /// Gets the first item (terminal operation - executes until first match).
195 ///
196 /// # Example
197 ///
198 /// ```ignore
199 /// let first = query.first();
200 /// ```
201 pub fn first(mut self) -> Option<&'a T> {
202 self.iter.next()
203 }
204
205 /// Counts items (terminal operation - executes query).
206 ///
207 /// # Example
208 ///
209 /// ```ignore
210 /// let count = query.count();
211 /// ```
212 pub fn count(self) -> usize {
213 self.iter.count()
214 }
215
216 /// Checks if any items match (terminal operation - short-circuits).
217 ///
218 /// # Example
219 ///
220 /// ```ignore
221 /// let exists = query.any();
222 /// ```
223 pub fn any(mut self) -> bool {
224 self.iter.next().is_some()
225 }
226
227 /// Executes a function for each item (terminal operation).
228 ///
229 /// # Example
230 ///
231 /// ```ignore
232 /// query.for_each(|item| println!("{:?}", item));
233 /// ```
234 pub fn for_each<F>(self, f: F)
235 where
236 F: FnMut(&'a T),
237 {
238 self.iter.for_each(f)
239 }
240
241 /// Folds the iterator (terminal operation).
242 ///
243 /// # Example
244 ///
245 /// ```ignore
246 /// let sum = query.fold(0.0, |acc, item| acc + item.price);
247 /// ```
248 pub fn fold<B, F>(self, init: B, f: F) -> B
249 where
250 F: FnMut(B, &'a T) -> B,
251 {
252 self.iter.fold(init, f)
253 }
254
255 /// Finds an item matching a predicate (terminal - short-circuits).
256 ///
257 /// # Example
258 ///
259 /// ```ignore
260 /// let found = query.find(|item| item.id == 42);
261 /// ```
262 pub fn find<P>(mut self, predicate: P) -> Option<&'a T>
263 where
264 P: FnMut(&&'a T) -> bool,
265 {
266 self.iter.find(predicate)
267 }
268
269 /// Checks if all items match a predicate (terminal - short-circuits).
270 ///
271 /// # Example
272 ///
273 /// ```ignore
274 /// let all_positive = query.all_match(|item| item.value > 0);
275 /// ```
276 pub fn all_match<P>(mut self, mut predicate: P) -> bool
277 where
278 P: FnMut(&'a T) -> bool,
279 {
280 self.iter.all(move |item| predicate(item))
281 }
282
283 /// Collects all items into a vector (terminal operation - executes query).
284 ///
285 /// # Example
286 ///
287 /// ```ignore
288 /// let results: Vec<&Product> = query.all();
289 /// ```
290 pub fn all(self) -> Vec<&'a T> {
291 self.iter.collect()
292 }
293
294 /// Converts to a standard iterator for further chaining.
295 ///
296 /// # Example
297 ///
298 /// ```ignore
299 /// let custom: Vec<_> = query
300 /// .into_iter()
301 /// .map(|item| item.custom_transform())
302 /// .filter(|x| x.is_valid())
303 /// .collect();
304 /// ```
305 pub fn into_iter(self) -> I {
306 self.iter
307 }
308}
309
310// Aggregation operations
311impl<'a, T: 'static, I> LazyQuery<'a, T, I>
312where
313 I: Iterator<Item = &'a T> + 'a,
314{
315 /// Computes sum of a field (terminal operation).
316 ///
317 /// # Example
318 ///
319 /// ```ignore
320 /// let total: f64 = LazyQuery::new(&products)
321 /// .sum_by(Product::price_r());
322 /// ```
323 pub fn sum_by<F>(self, path: KeyPaths<T, F>) -> F
324 where
325 F: Clone + std::ops::Add<Output = F> + Default + 'static,
326 I: 'a,
327 {
328 self.iter
329 .filter_map(move |item| path.get(item).cloned())
330 .fold(F::default(), |acc, val| acc + val)
331 }
332
333 /// Computes average of a float field (terminal operation).
334 ///
335 /// # Example
336 ///
337 /// ```ignore
338 /// let avg = LazyQuery::new(&products)
339 /// .avg_by(Product::price_r());
340 /// ```
341 pub fn avg_by(self, path: KeyPaths<T, f64>) -> Option<f64>
342 where
343 I: 'a,
344 {
345 let items: Vec<f64> = self
346 .iter
347 .filter_map(move |item| path.get(item).cloned())
348 .collect();
349
350 if items.is_empty() {
351 None
352 } else {
353 Some(items.iter().sum::<f64>() / items.len() as f64)
354 }
355 }
356
357 /// Finds minimum value of a field (terminal operation).
358 ///
359 /// # Example
360 ///
361 /// ```ignore
362 /// let min = LazyQuery::new(&products)
363 /// .min_by(Product::price_r());
364 /// ```
365 pub fn min_by<F>(self, path: KeyPaths<T, F>) -> Option<F>
366 where
367 F: Ord + Clone + 'static,
368 I: 'a,
369 {
370 self.iter.filter_map(move |item| path.get(item).cloned()).min()
371 }
372
373 /// Finds maximum value of a field (terminal operation).
374 ///
375 /// # Example
376 ///
377 /// ```ignore
378 /// let max = LazyQuery::new(&products)
379 /// .max_by(Product::price_r());
380 /// ```
381 pub fn max_by<F>(self, path: KeyPaths<T, F>) -> Option<F>
382 where
383 F: Ord + Clone + 'static,
384 I: 'a,
385 {
386 self.iter.filter_map(move |item| path.get(item).cloned()).max()
387 }
388
389 /// Finds minimum float value (terminal operation).
390 pub fn min_by_float(self, path: KeyPaths<T, f64>) -> Option<f64>
391 where
392 I: 'a,
393 {
394 self.iter
395 .filter_map(move |item| path.get(item).cloned())
396 .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
397 }
398
399 /// Finds maximum float value (terminal operation).
400 pub fn max_by_float(self, path: KeyPaths<T, f64>) -> Option<f64>
401 where
402 I: 'a,
403 {
404 self.iter
405 .filter_map(move |item| path.get(item).cloned())
406 .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
407 }
408
409 // DateTime operations for SystemTime (lazy)
410 /// Filter by SystemTime being after a reference time (lazy).
411 ///
412 /// # Arguments
413 ///
414 /// * `path` - The key-path to the SystemTime field
415 /// * `reference` - The reference time to compare against
416 ///
417 /// # Example
418 ///
419 /// ```ignore
420 /// let recent = LazyQuery::new(&events)
421 /// .where_after_systemtime(Event::timestamp_r(), cutoff_time)
422 /// .collect::<Vec<_>>();
423 /// ```
424 pub fn where_after_systemtime(self, path: KeyPaths<T, SystemTime>, reference: SystemTime) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a> {
425 self.where_(path, move |time| time > &reference)
426 }
427
428 /// Filter by SystemTime being before a reference time (lazy).
429 ///
430 /// # Arguments
431 ///
432 /// * `path` - The key-path to the SystemTime field
433 /// * `reference` - The reference time to compare against
434 ///
435 /// # Example
436 ///
437 /// ```ignore
438 /// let old = LazyQuery::new(&events)
439 /// .where_before_systemtime(Event::timestamp_r(), cutoff_time)
440 /// .collect::<Vec<_>>();
441 /// ```
442 pub fn where_before_systemtime(self, path: KeyPaths<T, SystemTime>, reference: SystemTime) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a> {
443 self.where_(path, move |time| time < &reference)
444 }
445
446 /// Filter by SystemTime being between two times (inclusive, lazy).
447 ///
448 /// # Arguments
449 ///
450 /// * `path` - The key-path to the SystemTime field
451 /// * `start` - The start time
452 /// * `end` - The end time
453 ///
454 /// # Example
455 ///
456 /// ```ignore
457 /// let range = LazyQuery::new(&events)
458 /// .where_between_systemtime(Event::timestamp_r(), start, end)
459 /// .collect::<Vec<_>>();
460 /// ```
461 pub fn where_between_systemtime(
462 self,
463 path: KeyPaths<T, SystemTime>,
464 start: SystemTime,
465 end: SystemTime,
466 ) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a> {
467 self.where_(path, move |time| time >= &start && time <= &end)
468 }
469}
470
471// DateTime operations with chrono (only available with datetime feature, lazy)
472#[cfg(feature = "datetime")]
473impl<'a, T: 'static, I> LazyQuery<'a, T, I>
474where
475 I: Iterator<Item = &'a T> + 'a,
476{
477 /// Filter by DateTime being after a reference time (lazy).
478 ///
479 /// # Arguments
480 ///
481 /// * `path` - The key-path to the DateTime field
482 /// * `reference` - The reference time to compare against
483 ///
484 /// # Example
485 ///
486 /// ```ignore
487 /// let recent = LazyQuery::new(&events)
488 /// .where_after(Event::timestamp_r(), cutoff_time)
489 /// .collect::<Vec<_>>();
490 /// ```
491 pub fn where_after<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, reference: DateTime<Tz>) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
492 where
493 Tz: TimeZone + 'static,
494 Tz::Offset: std::fmt::Display,
495 {
496 self.where_(path, move |time| time > &reference)
497 }
498
499 /// Filter by DateTime being before a reference time (lazy).
500 ///
501 /// # Arguments
502 ///
503 /// * `path` - The key-path to the DateTime field
504 /// * `reference` - The reference time to compare against
505 ///
506 /// # Example
507 ///
508 /// ```ignore
509 /// let old = LazyQuery::new(&events)
510 /// .where_before(Event::timestamp_r(), cutoff_time)
511 /// .collect::<Vec<_>>();
512 /// ```
513 pub fn where_before<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, reference: DateTime<Tz>) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
514 where
515 Tz: TimeZone + 'static,
516 Tz::Offset: std::fmt::Display,
517 {
518 self.where_(path, move |time| time < &reference)
519 }
520
521 /// Filter by DateTime being between two times (inclusive, lazy).
522 ///
523 /// # Arguments
524 ///
525 /// * `path` - The key-path to the DateTime field
526 /// * `start` - The start time
527 /// * `end` - The end time
528 ///
529 /// # Example
530 ///
531 /// ```ignore
532 /// let range = LazyQuery::new(&events)
533 /// .where_between(Event::timestamp_r(), start, end)
534 /// .collect::<Vec<_>>();
535 /// ```
536 pub fn where_between<Tz>(
537 self,
538 path: KeyPaths<T, DateTime<Tz>>,
539 start: DateTime<Tz>,
540 end: DateTime<Tz>,
541 ) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
542 where
543 Tz: TimeZone + 'static,
544 Tz::Offset: std::fmt::Display,
545 {
546 self.where_(path, move |time| time >= &start && time <= &end)
547 }
548
549 /// Filter by DateTime being today (lazy).
550 ///
551 /// # Arguments
552 ///
553 /// * `path` - The key-path to the DateTime field
554 /// * `now` - The current DateTime to compare against
555 ///
556 /// # Example
557 ///
558 /// ```ignore
559 /// let today = LazyQuery::new(&events)
560 /// .where_today(Event::timestamp_r(), Utc::now())
561 /// .collect::<Vec<_>>();
562 /// ```
563 pub fn where_today<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, now: DateTime<Tz>) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
564 where
565 Tz: TimeZone + 'static,
566 Tz::Offset: std::fmt::Display,
567 {
568 self.where_(path, move |time| {
569 time.date_naive() == now.date_naive()
570 })
571 }
572
573 /// Filter by DateTime year (lazy).
574 ///
575 /// # Arguments
576 ///
577 /// * `path` - The key-path to the DateTime field
578 /// * `year` - The year to filter by
579 ///
580 /// # Example
581 ///
582 /// ```ignore
583 /// let this_year = LazyQuery::new(&events)
584 /// .where_year(Event::timestamp_r(), 2024)
585 /// .collect::<Vec<_>>();
586 /// ```
587 pub fn where_year<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, year: i32) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
588 where
589 Tz: TimeZone + 'static,
590 Tz::Offset: std::fmt::Display,
591 {
592 use chrono::Datelike;
593 self.where_(path, move |time| time.year() == year)
594 }
595
596 /// Filter by DateTime month (lazy).
597 ///
598 /// # Arguments
599 ///
600 /// * `path` - The key-path to the DateTime field
601 /// * `month` - The month to filter by (1-12)
602 ///
603 /// # Example
604 ///
605 /// ```ignore
606 /// let december = LazyQuery::new(&events)
607 /// .where_month(Event::timestamp_r(), 12)
608 /// .collect::<Vec<_>>();
609 /// ```
610 pub fn where_month<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, month: u32) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
611 where
612 Tz: TimeZone + 'static,
613 Tz::Offset: std::fmt::Display,
614 {
615 use chrono::Datelike;
616 self.where_(path, move |time| time.month() == month)
617 }
618
619 /// Filter by DateTime day (lazy).
620 ///
621 /// # Arguments
622 ///
623 /// * `path` - The key-path to the DateTime field
624 /// * `day` - The day to filter by (1-31)
625 ///
626 /// # Example
627 ///
628 /// ```ignore
629 /// let first = LazyQuery::new(&events)
630 /// .where_day(Event::timestamp_r(), 1)
631 /// .collect::<Vec<_>>();
632 /// ```
633 pub fn where_day<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, day: u32) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
634 where
635 Tz: TimeZone + 'static,
636 Tz::Offset: std::fmt::Display,
637 {
638 use chrono::Datelike;
639 self.where_(path, move |time| time.day() == day)
640 }
641
642 /// Filter by weekend dates (Saturday and Sunday, lazy).
643 ///
644 /// # Arguments
645 ///
646 /// * `path` - The key-path to the DateTime field
647 ///
648 /// # Example
649 ///
650 /// ```ignore
651 /// let weekend_events = LazyQuery::new(&events)
652 /// .where_weekend(Event::timestamp_r())
653 /// .collect::<Vec<_>>();
654 /// ```
655 pub fn where_weekend<Tz>(self, path: KeyPaths<T, DateTime<Tz>>) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
656 where
657 Tz: TimeZone + 'static,
658 Tz::Offset: std::fmt::Display,
659 {
660 use chrono::Datelike;
661 self.where_(path, |time| {
662 let weekday = time.weekday().num_days_from_monday();
663 weekday >= 5
664 })
665 }
666
667 /// Filter by weekday dates (Monday through Friday, lazy).
668 ///
669 /// # Arguments
670 ///
671 /// * `path` - The key-path to the DateTime field
672 ///
673 /// # Example
674 ///
675 /// ```ignore
676 /// let weekday_events = LazyQuery::new(&events)
677 /// .where_weekday(Event::timestamp_r())
678 /// .collect::<Vec<_>>();
679 /// ```
680 pub fn where_weekday<Tz>(self, path: KeyPaths<T, DateTime<Tz>>) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
681 where
682 Tz: TimeZone + 'static,
683 Tz::Offset: std::fmt::Display,
684 {
685 use chrono::Datelike;
686 self.where_(path, |time| {
687 let weekday = time.weekday().num_days_from_monday();
688 weekday < 5
689 })
690 }
691
692 /// Filter by business hours (9 AM - 5 PM, lazy).
693 ///
694 /// # Arguments
695 ///
696 /// * `path` - The key-path to the DateTime field
697 ///
698 /// # Example
699 ///
700 /// ```ignore
701 /// let business_hours = LazyQuery::new(&events)
702 /// .where_business_hours(Event::timestamp_r())
703 /// .collect::<Vec<_>>();
704 /// ```
705 pub fn where_business_hours<Tz>(self, path: KeyPaths<T, DateTime<Tz>>) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
706 where
707 Tz: TimeZone + 'static,
708 Tz::Offset: std::fmt::Display,
709 {
710 use chrono::Timelike;
711 self.where_(path, |time| {
712 let hour = time.hour();
713 hour >= 9 && hour < 17
714 })
715 }
716}
717
718// Enable using LazyQuery in for loops
719impl<'a, T: 'static, I> IntoIterator for LazyQuery<'a, T, I>
720where
721 I: Iterator<Item = &'a T>,
722{
723 type Item = &'a T;
724 type IntoIter = I;
725
726 fn into_iter(self) -> Self::IntoIter {
727 self.iter
728 }
729}
730