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