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 /// Converts to a standard iterator for further chaining.
261 ///
262 /// # Example
263 ///
264 /// ```ignore
265 /// let custom: Vec<_> = query
266 /// .into_iter()
267 /// .map(|item| item.custom_transform())
268 /// .filter(|x| x.is_valid())
269 /// .collect();
270 /// ```
271 pub fn into_iter(self) -> I {
272 self.iter
273 }
274}
275
276// Aggregation operations
277impl<'a, T: 'static, I> LazyQuery<'a, T, I>
278where
279 I: Iterator<Item = &'a T> + 'a,
280{
281 /// Computes sum of a field (terminal operation).
282 ///
283 /// # Example
284 ///
285 /// ```ignore
286 /// let total: f64 = LazyQuery::new(&products)
287 /// .sum_by(Product::price_r());
288 /// ```
289 pub fn sum_by<F>(self, path: KeyPaths<T, F>) -> F
290 where
291 F: Clone + std::ops::Add<Output = F> + Default + 'static,
292 I: 'a,
293 {
294 self.iter
295 .filter_map(move |item| path.get(item).cloned())
296 .fold(F::default(), |acc, val| acc + val)
297 }
298
299 /// Computes average of a float field (terminal operation).
300 ///
301 /// # Example
302 ///
303 /// ```ignore
304 /// let avg = LazyQuery::new(&products)
305 /// .avg_by(Product::price_r());
306 /// ```
307 pub fn avg_by(self, path: KeyPaths<T, f64>) -> Option<f64>
308 where
309 I: 'a,
310 {
311 let items: Vec<f64> = self
312 .iter
313 .filter_map(move |item| path.get(item).cloned())
314 .collect();
315
316 if items.is_empty() {
317 None
318 } else {
319 Some(items.iter().sum::<f64>() / items.len() as f64)
320 }
321 }
322
323 /// Finds minimum value of a field (terminal operation).
324 ///
325 /// # Example
326 ///
327 /// ```ignore
328 /// let min = LazyQuery::new(&products)
329 /// .min_by(Product::price_r());
330 /// ```
331 pub fn min_by<F>(self, path: KeyPaths<T, F>) -> Option<F>
332 where
333 F: Ord + Clone + 'static,
334 I: 'a,
335 {
336 self.iter.filter_map(move |item| path.get(item).cloned()).min()
337 }
338
339 /// Finds maximum value of a field (terminal operation).
340 ///
341 /// # Example
342 ///
343 /// ```ignore
344 /// let max = LazyQuery::new(&products)
345 /// .max_by(Product::price_r());
346 /// ```
347 pub fn max_by<F>(self, path: KeyPaths<T, F>) -> Option<F>
348 where
349 F: Ord + Clone + 'static,
350 I: 'a,
351 {
352 self.iter.filter_map(move |item| path.get(item).cloned()).max()
353 }
354
355 /// Finds minimum float value (terminal operation).
356 pub fn min_by_float(self, path: KeyPaths<T, f64>) -> Option<f64>
357 where
358 I: 'a,
359 {
360 self.iter
361 .filter_map(move |item| path.get(item).cloned())
362 .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
363 }
364
365 /// Finds maximum float value (terminal operation).
366 pub fn max_by_float(self, path: KeyPaths<T, f64>) -> Option<f64>
367 where
368 I: 'a,
369 {
370 self.iter
371 .filter_map(move |item| path.get(item).cloned())
372 .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
373 }
374
375 // DateTime operations for SystemTime (lazy)
376 /// Filter by SystemTime being after a reference time (lazy).
377 ///
378 /// # Arguments
379 ///
380 /// * `path` - The key-path to the SystemTime field
381 /// * `reference` - The reference time to compare against
382 ///
383 /// # Example
384 ///
385 /// ```ignore
386 /// let recent = LazyQuery::new(&events)
387 /// .where_after_systemtime(Event::timestamp_r(), cutoff_time)
388 /// .collect::<Vec<_>>();
389 /// ```
390 pub fn where_after_systemtime(self, path: KeyPaths<T, SystemTime>, reference: SystemTime) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a> {
391 self.where_(path, move |time| time > &reference)
392 }
393
394 /// Filter by SystemTime being before a reference time (lazy).
395 ///
396 /// # Arguments
397 ///
398 /// * `path` - The key-path to the SystemTime field
399 /// * `reference` - The reference time to compare against
400 ///
401 /// # Example
402 ///
403 /// ```ignore
404 /// let old = LazyQuery::new(&events)
405 /// .where_before_systemtime(Event::timestamp_r(), cutoff_time)
406 /// .collect::<Vec<_>>();
407 /// ```
408 pub fn where_before_systemtime(self, path: KeyPaths<T, SystemTime>, reference: SystemTime) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a> {
409 self.where_(path, move |time| time < &reference)
410 }
411
412 /// Filter by SystemTime being between two times (inclusive, lazy).
413 ///
414 /// # Arguments
415 ///
416 /// * `path` - The key-path to the SystemTime field
417 /// * `start` - The start time
418 /// * `end` - The end time
419 ///
420 /// # Example
421 ///
422 /// ```ignore
423 /// let range = LazyQuery::new(&events)
424 /// .where_between_systemtime(Event::timestamp_r(), start, end)
425 /// .collect::<Vec<_>>();
426 /// ```
427 pub fn where_between_systemtime(
428 self,
429 path: KeyPaths<T, SystemTime>,
430 start: SystemTime,
431 end: SystemTime,
432 ) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a> {
433 self.where_(path, move |time| time >= &start && time <= &end)
434 }
435}
436
437// DateTime operations with chrono (only available with datetime feature, lazy)
438#[cfg(feature = "datetime")]
439impl<'a, T: 'static, I> LazyQuery<'a, T, I>
440where
441 I: Iterator<Item = &'a T> + 'a,
442{
443 /// Filter by DateTime being after a reference time (lazy).
444 ///
445 /// # Arguments
446 ///
447 /// * `path` - The key-path to the DateTime field
448 /// * `reference` - The reference time to compare against
449 ///
450 /// # Example
451 ///
452 /// ```ignore
453 /// let recent = LazyQuery::new(&events)
454 /// .where_after(Event::timestamp_r(), cutoff_time)
455 /// .collect::<Vec<_>>();
456 /// ```
457 pub fn where_after<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, reference: DateTime<Tz>) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
458 where
459 Tz: TimeZone + 'static,
460 Tz::Offset: std::fmt::Display,
461 {
462 self.where_(path, move |time| time > &reference)
463 }
464
465 /// Filter by DateTime being before a reference time (lazy).
466 ///
467 /// # Arguments
468 ///
469 /// * `path` - The key-path to the DateTime field
470 /// * `reference` - The reference time to compare against
471 ///
472 /// # Example
473 ///
474 /// ```ignore
475 /// let old = LazyQuery::new(&events)
476 /// .where_before(Event::timestamp_r(), cutoff_time)
477 /// .collect::<Vec<_>>();
478 /// ```
479 pub fn where_before<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, reference: DateTime<Tz>) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
480 where
481 Tz: TimeZone + 'static,
482 Tz::Offset: std::fmt::Display,
483 {
484 self.where_(path, move |time| time < &reference)
485 }
486
487 /// Filter by DateTime being between two times (inclusive, lazy).
488 ///
489 /// # Arguments
490 ///
491 /// * `path` - The key-path to the DateTime field
492 /// * `start` - The start time
493 /// * `end` - The end time
494 ///
495 /// # Example
496 ///
497 /// ```ignore
498 /// let range = LazyQuery::new(&events)
499 /// .where_between(Event::timestamp_r(), start, end)
500 /// .collect::<Vec<_>>();
501 /// ```
502 pub fn where_between<Tz>(
503 self,
504 path: KeyPaths<T, DateTime<Tz>>,
505 start: DateTime<Tz>,
506 end: DateTime<Tz>,
507 ) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
508 where
509 Tz: TimeZone + 'static,
510 Tz::Offset: std::fmt::Display,
511 {
512 self.where_(path, move |time| time >= &start && time <= &end)
513 }
514
515 /// Filter by DateTime being today (lazy).
516 ///
517 /// # Arguments
518 ///
519 /// * `path` - The key-path to the DateTime field
520 /// * `now` - The current DateTime to compare against
521 ///
522 /// # Example
523 ///
524 /// ```ignore
525 /// let today = LazyQuery::new(&events)
526 /// .where_today(Event::timestamp_r(), Utc::now())
527 /// .collect::<Vec<_>>();
528 /// ```
529 pub fn where_today<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, now: DateTime<Tz>) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
530 where
531 Tz: TimeZone + 'static,
532 Tz::Offset: std::fmt::Display,
533 {
534 self.where_(path, move |time| {
535 time.date_naive() == now.date_naive()
536 })
537 }
538
539 /// Filter by DateTime year (lazy).
540 ///
541 /// # Arguments
542 ///
543 /// * `path` - The key-path to the DateTime field
544 /// * `year` - The year to filter by
545 ///
546 /// # Example
547 ///
548 /// ```ignore
549 /// let this_year = LazyQuery::new(&events)
550 /// .where_year(Event::timestamp_r(), 2024)
551 /// .collect::<Vec<_>>();
552 /// ```
553 pub fn where_year<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, year: i32) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
554 where
555 Tz: TimeZone + 'static,
556 Tz::Offset: std::fmt::Display,
557 {
558 use chrono::Datelike;
559 self.where_(path, move |time| time.year() == year)
560 }
561
562 /// Filter by DateTime month (lazy).
563 ///
564 /// # Arguments
565 ///
566 /// * `path` - The key-path to the DateTime field
567 /// * `month` - The month to filter by (1-12)
568 ///
569 /// # Example
570 ///
571 /// ```ignore
572 /// let december = LazyQuery::new(&events)
573 /// .where_month(Event::timestamp_r(), 12)
574 /// .collect::<Vec<_>>();
575 /// ```
576 pub fn where_month<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, month: u32) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
577 where
578 Tz: TimeZone + 'static,
579 Tz::Offset: std::fmt::Display,
580 {
581 use chrono::Datelike;
582 self.where_(path, move |time| time.month() == month)
583 }
584
585 /// Filter by DateTime day (lazy).
586 ///
587 /// # Arguments
588 ///
589 /// * `path` - The key-path to the DateTime field
590 /// * `day` - The day to filter by (1-31)
591 ///
592 /// # Example
593 ///
594 /// ```ignore
595 /// let first = LazyQuery::new(&events)
596 /// .where_day(Event::timestamp_r(), 1)
597 /// .collect::<Vec<_>>();
598 /// ```
599 pub fn where_day<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, day: u32) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
600 where
601 Tz: TimeZone + 'static,
602 Tz::Offset: std::fmt::Display,
603 {
604 use chrono::Datelike;
605 self.where_(path, move |time| time.day() == day)
606 }
607
608 /// Filter by weekend dates (Saturday and Sunday, lazy).
609 ///
610 /// # Arguments
611 ///
612 /// * `path` - The key-path to the DateTime field
613 ///
614 /// # Example
615 ///
616 /// ```ignore
617 /// let weekend_events = LazyQuery::new(&events)
618 /// .where_weekend(Event::timestamp_r())
619 /// .collect::<Vec<_>>();
620 /// ```
621 pub fn where_weekend<Tz>(self, path: KeyPaths<T, DateTime<Tz>>) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
622 where
623 Tz: TimeZone + 'static,
624 Tz::Offset: std::fmt::Display,
625 {
626 use chrono::Datelike;
627 self.where_(path, |time| {
628 let weekday = time.weekday().num_days_from_monday();
629 weekday >= 5
630 })
631 }
632
633 /// Filter by weekday dates (Monday through Friday, lazy).
634 ///
635 /// # Arguments
636 ///
637 /// * `path` - The key-path to the DateTime field
638 ///
639 /// # Example
640 ///
641 /// ```ignore
642 /// let weekday_events = LazyQuery::new(&events)
643 /// .where_weekday(Event::timestamp_r())
644 /// .collect::<Vec<_>>();
645 /// ```
646 pub fn where_weekday<Tz>(self, path: KeyPaths<T, DateTime<Tz>>) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
647 where
648 Tz: TimeZone + 'static,
649 Tz::Offset: std::fmt::Display,
650 {
651 use chrono::Datelike;
652 self.where_(path, |time| {
653 let weekday = time.weekday().num_days_from_monday();
654 weekday < 5
655 })
656 }
657
658 /// Filter by business hours (9 AM - 5 PM, lazy).
659 ///
660 /// # Arguments
661 ///
662 /// * `path` - The key-path to the DateTime field
663 ///
664 /// # Example
665 ///
666 /// ```ignore
667 /// let business_hours = LazyQuery::new(&events)
668 /// .where_business_hours(Event::timestamp_r())
669 /// .collect::<Vec<_>>();
670 /// ```
671 pub fn where_business_hours<Tz>(self, path: KeyPaths<T, DateTime<Tz>>) -> LazyQuery<'a, T, impl Iterator<Item = &'a T> + 'a>
672 where
673 Tz: TimeZone + 'static,
674 Tz::Offset: std::fmt::Display,
675 {
676 use chrono::Timelike;
677 self.where_(path, |time| {
678 let hour = time.hour();
679 hour >= 9 && hour < 17
680 })
681 }
682}
683
684// Enable using LazyQuery in for loops
685impl<'a, T: 'static, I> IntoIterator for LazyQuery<'a, T, I>
686where
687 I: Iterator<Item = &'a T>,
688{
689 type Item = &'a T;
690 type IntoIter = I;
691
692 fn into_iter(self) -> Self::IntoIter {
693 self.iter
694 }
695}
696