rust_queries_core/query.rs
1//! Query builder implementation for filtering, selecting, ordering, grouping, and aggregating data.
2//!
3//! This module provides the `Query` struct which enables SQL-like operations on collections
4//! using type-safe key-paths.
5
6use key_paths_core::KeyPaths;
7use std::collections::HashMap;
8use std::time::SystemTime;
9
10#[cfg(feature = "datetime")]
11use chrono::{DateTime, TimeZone};
12
13/// A query builder for filtering, selecting, ordering, grouping, and aggregating data.
14///
15/// # Type Parameters
16///
17/// * `'a` - The lifetime of the data being queried
18/// * `T` - The type of items in the collection
19///
20/// # Example
21///
22/// ```ignore
23/// let products = vec![/* ... */];
24/// let query = Query::new(&products)
25/// .where_(Product::price(), |&price| price < 100.0)
26/// .order_by_float(Product::price());
27/// ```
28pub struct Query<'a, T: 'static> {
29 data: &'a [T],
30 filters: Vec<Box<dyn Fn(&T) -> bool>>,
31}
32
33// Core implementation without Clone requirement
34impl<'a, T: 'static> Query<'a, T> {
35 /// Creates a new query from a slice of data.
36 ///
37 /// # Arguments
38 ///
39 /// * `data` - A slice of items to query
40 ///
41 /// # Example
42 ///
43 /// ```ignore
44 /// let query = Query::new(&products);
45 /// ```
46 pub fn new(data: &'a [T]) -> Self {
47 Self {
48 data,
49 filters: Vec::new(),
50 }
51 }
52
53 /// Adds a filter predicate using a key-path.
54 ///
55 /// # Arguments
56 ///
57 /// * `path` - The key-path to the field to filter on
58 /// * `predicate` - A function that returns true for items to keep
59 ///
60 /// # Example
61 ///
62 /// ```ignore
63 /// let query = Query::new(&products)
64 /// .where_(Product::category(), |cat| cat == "Electronics");
65 /// ```
66 pub fn where_<F>(mut self, path: KeyPaths<T, F>, predicate: impl Fn(&F) -> bool + 'static) -> Self
67 where
68 F: 'static,
69 {
70 self.filters.push(Box::new(move |item| {
71 path.get(item).map_or(false, |val| predicate(val))
72 }));
73 self
74 }
75
76 /// Returns all items matching the query filters.
77 ///
78 /// # Example
79 ///
80 /// ```ignore
81 /// let results = query.all();
82 /// ```
83 pub fn all(&self) -> Vec<&T> {
84 self.data
85 .iter()
86 .filter(|item| self.filters.iter().all(|f| f(item)))
87 .collect()
88 }
89
90 /// Returns the first item matching the query filters.
91 ///
92 /// # Example
93 ///
94 /// ```ignore
95 /// let first = query.first();
96 /// ```
97 pub fn first(&self) -> Option<&T> {
98 self.data
99 .iter()
100 .find(|item| self.filters.iter().all(|f| f(item)))
101 }
102
103 /// Returns the count of items matching the query filters.
104 ///
105 /// # Example
106 ///
107 /// ```ignore
108 /// let count = query.count();
109 /// ```
110 pub fn count(&self) -> usize {
111 self.data
112 .iter()
113 .filter(|item| self.filters.iter().all(|f| f(item)))
114 .count()
115 }
116
117 /// Returns the first `n` items matching the query filters.
118 ///
119 /// # Arguments
120 ///
121 /// * `n` - The maximum number of items to return
122 ///
123 /// # Example
124 ///
125 /// ```ignore
126 /// let first_10 = query.limit(10);
127 /// ```
128 pub fn limit(&self, n: usize) -> Vec<&T> {
129 self.data
130 .iter()
131 .filter(|item| self.filters.iter().all(|f| f(item)))
132 .take(n)
133 .collect()
134 }
135
136 /// Skips the first `offset` items for pagination.
137 ///
138 /// # Arguments
139 ///
140 /// * `offset` - The number of items to skip
141 ///
142 /// # Example
143 ///
144 /// ```ignore
145 /// let page_2 = query.skip(20).limit(10);
146 /// ```
147 pub fn skip<'b>(&'b self, offset: usize) -> QueryWithSkip<'a, 'b, T> {
148 QueryWithSkip {
149 query: self,
150 offset,
151 }
152 }
153
154 /// Projects/selects a single field from results.
155 ///
156 /// # Arguments
157 ///
158 /// * `path` - The key-path to the field to select
159 ///
160 /// # Example
161 ///
162 /// ```ignore
163 /// let names = query.select(Product::name());
164 /// ```
165 pub fn select<F>(&self, path: KeyPaths<T, F>) -> Vec<F>
166 where
167 F: Clone + 'static,
168 {
169 self.data
170 .iter()
171 .filter(|item| self.filters.iter().all(|f| f(item)))
172 .filter_map(|item| path.get(item).cloned())
173 .collect()
174 }
175
176 /// Computes the sum of a numeric field.
177 ///
178 /// # Arguments
179 ///
180 /// * `path` - The key-path to the numeric field
181 ///
182 /// # Example
183 ///
184 /// ```ignore
185 /// let total_price = query.sum(Product::price());
186 /// ```
187 pub fn sum<F>(&self, path: KeyPaths<T, F>) -> F
188 where
189 F: Clone + std::ops::Add<Output = F> + Default + 'static,
190 {
191 self.data
192 .iter()
193 .filter(|item| self.filters.iter().all(|f| f(item)))
194 .filter_map(|item| path.get(item).cloned())
195 .fold(F::default(), |acc, val| acc + val)
196 }
197
198 /// Computes the average of a float field.
199 ///
200 /// # Arguments
201 ///
202 /// * `path` - The key-path to the f64 field
203 ///
204 /// # Example
205 ///
206 /// ```ignore
207 /// let avg_price = query.avg(Product::price()).unwrap_or(0.0);
208 /// ```
209 pub fn avg(&self, path: KeyPaths<T, f64>) -> Option<f64> {
210 let items: Vec<f64> = self
211 .data
212 .iter()
213 .filter(|item| self.filters.iter().all(|f| f(item)))
214 .filter_map(|item| path.get(item).cloned())
215 .collect();
216
217 if items.is_empty() {
218 None
219 } else {
220 Some(items.iter().sum::<f64>() / items.len() as f64)
221 }
222 }
223
224 /// Finds the minimum value of a field.
225 ///
226 /// # Arguments
227 ///
228 /// * `path` - The key-path to the field
229 ///
230 /// # Example
231 ///
232 /// ```ignore
233 /// let min_stock = query.min(Product::stock());
234 /// ```
235 pub fn min<F>(&self, path: KeyPaths<T, F>) -> Option<F>
236 where
237 F: Ord + Clone + 'static,
238 {
239 self.data
240 .iter()
241 .filter(|item| self.filters.iter().all(|f| f(item)))
242 .filter_map(|item| path.get(item).cloned())
243 .min()
244 }
245
246 /// Finds the maximum value of a field.
247 ///
248 /// # Arguments
249 ///
250 /// * `path` - The key-path to the field
251 ///
252 /// # Example
253 ///
254 /// ```ignore
255 /// let max_stock = query.max(Product::stock());
256 /// ```
257 pub fn max<F>(&self, path: KeyPaths<T, F>) -> Option<F>
258 where
259 F: Ord + Clone + 'static,
260 {
261 self.data
262 .iter()
263 .filter(|item| self.filters.iter().all(|f| f(item)))
264 .filter_map(|item| path.get(item).cloned())
265 .max()
266 }
267
268 /// Finds the minimum value of a float field.
269 ///
270 /// # Arguments
271 ///
272 /// * `path` - The key-path to the f64 field
273 ///
274 /// # Example
275 ///
276 /// ```ignore
277 /// let min_price = query.min_float(Product::price());
278 /// ```
279 pub fn min_float(&self, path: KeyPaths<T, f64>) -> Option<f64> {
280 self.data
281 .iter()
282 .filter(|item| self.filters.iter().all(|f| f(item)))
283 .filter_map(|item| path.get(item).cloned())
284 .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
285 }
286
287 /// Finds the maximum value of a float field.
288 ///
289 /// # Arguments
290 ///
291 /// * `path` - The key-path to the f64 field
292 ///
293 /// # Example
294 ///
295 /// ```ignore
296 /// let max_price = query.max_float(Product::price());
297 /// ```
298 pub fn max_float(&self, path: KeyPaths<T, f64>) -> Option<f64> {
299 self.data
300 .iter()
301 .filter(|item| self.filters.iter().all(|f| f(item)))
302 .filter_map(|item| path.get(item).cloned())
303 .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
304 }
305
306 /// Checks if any items match the query filters.
307 ///
308 /// # Example
309 ///
310 /// ```ignore
311 /// let has_results = query.exists();
312 /// ```
313 pub fn exists(&self) -> bool {
314 self.data
315 .iter()
316 .any(|item| self.filters.iter().all(|f| f(item)))
317 }
318
319 // DateTime operations for SystemTime
320 /// Filter by SystemTime being after a reference time.
321 ///
322 /// # Arguments
323 ///
324 /// * `path` - The key-path to the SystemTime field
325 /// * `reference` - The reference time to compare against
326 ///
327 /// # Example
328 ///
329 /// ```ignore
330 /// let recent = query.where_after_systemtime(Event::timestamp(), &cutoff_time);
331 /// ```
332 pub fn where_after_systemtime(self, path: KeyPaths<T, SystemTime>, reference: SystemTime) -> Self {
333 self.where_(path, move |time| time > &reference)
334 }
335
336 /// Filter by SystemTime being before a reference time.
337 ///
338 /// # Arguments
339 ///
340 /// * `path` - The key-path to the SystemTime field
341 /// * `reference` - The reference time to compare against
342 ///
343 /// # Example
344 ///
345 /// ```ignore
346 /// let old = query.where_before_systemtime(Event::timestamp(), &cutoff_time);
347 /// ```
348 pub fn where_before_systemtime(self, path: KeyPaths<T, SystemTime>, reference: SystemTime) -> Self {
349 self.where_(path, move |time| time < &reference)
350 }
351
352 /// Filter by SystemTime being between two times (inclusive).
353 ///
354 /// # Arguments
355 ///
356 /// * `path` - The key-path to the SystemTime field
357 /// * `start` - The start time
358 /// * `end` - The end time
359 ///
360 /// # Example
361 ///
362 /// ```ignore
363 /// let range = query.where_between_systemtime(Event::timestamp(), &start, &end);
364 /// ```
365 pub fn where_between_systemtime(
366 self,
367 path: KeyPaths<T, SystemTime>,
368 start: SystemTime,
369 end: SystemTime,
370 ) -> Self {
371 self.where_(path, move |time| time >= &start && time <= &end)
372 }
373}
374
375// DateTime operations with chrono (only available with datetime feature)
376#[cfg(feature = "datetime")]
377impl<'a, T: 'static> Query<'a, T> {
378 /// Filter by DateTime being after a reference time.
379 ///
380 /// # Arguments
381 ///
382 /// * `path` - The key-path to the DateTime field
383 /// * `reference` - The reference time to compare against
384 ///
385 /// # Example
386 ///
387 /// ```ignore
388 /// let recent = query.where_after(Event::timestamp(), &cutoff_time);
389 /// ```
390 pub fn where_after<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, reference: DateTime<Tz>) -> Self
391 where
392 Tz: TimeZone + 'static,
393 Tz::Offset: std::fmt::Display,
394 {
395 self.where_(path, move |time| time > &reference)
396 }
397
398 /// Filter by DateTime being before a reference time.
399 ///
400 /// # Arguments
401 ///
402 /// * `path` - The key-path to the DateTime field
403 /// * `reference` - The reference time to compare against
404 ///
405 /// # Example
406 ///
407 /// ```ignore
408 /// let old = query.where_before(Event::timestamp(), &cutoff_time);
409 /// ```
410 pub fn where_before<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, reference: DateTime<Tz>) -> Self
411 where
412 Tz: TimeZone + 'static,
413 Tz::Offset: std::fmt::Display,
414 {
415 self.where_(path, move |time| time < &reference)
416 }
417
418 /// Filter by DateTime being between two times (inclusive).
419 ///
420 /// # Arguments
421 ///
422 /// * `path` - The key-path to the DateTime field
423 /// * `start` - The start time
424 /// * `end` - The end time
425 ///
426 /// # Example
427 ///
428 /// ```ignore
429 /// let range = query.where_between(Event::timestamp(), &start, &end);
430 /// ```
431 pub fn where_between<Tz>(
432 self,
433 path: KeyPaths<T, DateTime<Tz>>,
434 start: DateTime<Tz>,
435 end: DateTime<Tz>,
436 ) -> Self
437 where
438 Tz: TimeZone + 'static,
439 Tz::Offset: std::fmt::Display,
440 {
441 self.where_(path, move |time| time >= &start && time <= &end)
442 }
443
444 /// Filter by DateTime being today.
445 ///
446 /// # Arguments
447 ///
448 /// * `path` - The key-path to the DateTime field
449 /// * `now` - The current DateTime to compare against
450 ///
451 /// # Example
452 ///
453 /// ```ignore
454 /// let today = query.where_today(Event::timestamp(), &Utc::now());
455 /// ```
456 pub fn where_today<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, now: DateTime<Tz>) -> Self
457 where
458 Tz: TimeZone + 'static,
459 Tz::Offset: std::fmt::Display,
460 {
461 self.where_(path, move |time| {
462 time.date_naive() == now.date_naive()
463 })
464 }
465
466 /// Filter by DateTime year.
467 ///
468 /// # Arguments
469 ///
470 /// * `path` - The key-path to the DateTime field
471 /// * `year` - The year to filter by
472 ///
473 /// # Example
474 ///
475 /// ```ignore
476 /// let this_year = query.where_year(Event::timestamp(), 2024);
477 /// ```
478 pub fn where_year<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, year: i32) -> Self
479 where
480 Tz: TimeZone + 'static,
481 Tz::Offset: std::fmt::Display,
482 {
483 use chrono::Datelike;
484 self.where_(path, move |time| time.year() == year)
485 }
486
487 /// Filter by DateTime month.
488 ///
489 /// # Arguments
490 ///
491 /// * `path` - The key-path to the DateTime field
492 /// * `month` - The month to filter by (1-12)
493 ///
494 /// # Example
495 ///
496 /// ```ignore
497 /// let december = query.where_month(Event::timestamp(), 12);
498 /// ```
499 pub fn where_month<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, month: u32) -> Self
500 where
501 Tz: TimeZone + 'static,
502 Tz::Offset: std::fmt::Display,
503 {
504 use chrono::Datelike;
505 self.where_(path, move |time| time.month() == month)
506 }
507
508 /// Filter by DateTime day.
509 ///
510 /// # Arguments
511 ///
512 /// * `path` - The key-path to the DateTime field
513 /// * `day` - The day to filter by (1-31)
514 ///
515 /// # Example
516 ///
517 /// ```ignore
518 /// let first = query.where_day(Event::timestamp(), 1);
519 /// ```
520 pub fn where_day<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, day: u32) -> Self
521 where
522 Tz: TimeZone + 'static,
523 Tz::Offset: std::fmt::Display,
524 {
525 use chrono::Datelike;
526 self.where_(path, move |time| time.day() == day)
527 }
528
529 /// Filter by weekend dates (Saturday and Sunday).
530 ///
531 /// # Arguments
532 ///
533 /// * `path` - The key-path to the DateTime field
534 ///
535 /// # Example
536 ///
537 /// ```ignore
538 /// let weekend_events = query.where_weekend(Event::timestamp());
539 /// ```
540 pub fn where_weekend<Tz>(self, path: KeyPaths<T, DateTime<Tz>>) -> Self
541 where
542 Tz: TimeZone + 'static,
543 Tz::Offset: std::fmt::Display,
544 {
545 use chrono::Datelike;
546 self.where_(path, |time| {
547 let weekday = time.weekday().num_days_from_monday();
548 weekday >= 5
549 })
550 }
551
552 /// Filter by weekday dates (Monday through Friday).
553 ///
554 /// # Arguments
555 ///
556 /// * `path` - The key-path to the DateTime field
557 ///
558 /// # Example
559 ///
560 /// ```ignore
561 /// let weekday_events = query.where_weekday(Event::timestamp());
562 /// ```
563 pub fn where_weekday<Tz>(self, path: KeyPaths<T, DateTime<Tz>>) -> Self
564 where
565 Tz: TimeZone + 'static,
566 Tz::Offset: std::fmt::Display,
567 {
568 use chrono::Datelike;
569 self.where_(path, |time| {
570 let weekday = time.weekday().num_days_from_monday();
571 weekday < 5
572 })
573 }
574
575 /// Filter by business hours (9 AM - 5 PM).
576 ///
577 /// # Arguments
578 ///
579 /// * `path` - The key-path to the DateTime field
580 ///
581 /// # Example
582 ///
583 /// ```ignore
584 /// let business_hours = query.where_business_hours(Event::timestamp());
585 /// ```
586 pub fn where_business_hours<Tz>(self, path: KeyPaths<T, DateTime<Tz>>) -> Self
587 where
588 Tz: TimeZone + 'static,
589 Tz::Offset: std::fmt::Display,
590 {
591 use chrono::Timelike;
592 self.where_(path, |time| {
593 let hour = time.hour();
594 hour >= 9 && hour < 17
595 })
596 }
597}
598
599// Operations that require Clone - separated for flexibility
600impl<'a, T: 'static + Clone> Query<'a, T> {
601 /// Orders results by a field in ascending order.
602 ///
603 /// **Note**: This method requires `T: Clone` as it creates owned sorted copies.
604 ///
605 /// # Arguments
606 ///
607 /// * `path` - The key-path to the field to order by
608 ///
609 /// # Example
610 ///
611 /// ```ignore
612 /// let sorted = query.order_by(Product::name());
613 /// ```
614 pub fn order_by<F>(&self, path: KeyPaths<T, F>) -> Vec<T>
615 where
616 F: Ord + Clone + 'static,
617 {
618 let mut results: Vec<T> = self
619 .data
620 .iter()
621 .filter(|item| self.filters.iter().all(|f| f(item)))
622 .cloned()
623 .collect();
624
625 results.sort_by_key(|item| path.get(item).cloned());
626 results
627 }
628
629 /// Orders results by a field in descending order.
630 ///
631 /// **Note**: This method requires `T: Clone` as it creates owned sorted copies.
632 ///
633 /// # Arguments
634 ///
635 /// * `path` - The key-path to the field to order by
636 ///
637 /// # Example
638 ///
639 /// ```ignore
640 /// let sorted = query.order_by_desc(Product::stock());
641 /// ```
642 pub fn order_by_desc<F>(&self, path: KeyPaths<T, F>) -> Vec<T>
643 where
644 F: Ord + Clone + 'static,
645 {
646 let mut results: Vec<T> = self
647 .data
648 .iter()
649 .filter(|item| self.filters.iter().all(|f| f(item)))
650 .cloned()
651 .collect();
652
653 results.sort_by(|a, b| {
654 let a_val = path.get(a).cloned();
655 let b_val = path.get(b).cloned();
656 b_val.cmp(&a_val)
657 });
658 results
659 }
660
661 /// Orders results by a float field in ascending order.
662 ///
663 /// **Note**: This method requires `T: Clone` as it creates owned sorted copies.
664 ///
665 /// # Arguments
666 ///
667 /// * `path` - The key-path to the f64 field to order by
668 ///
669 /// # Example
670 ///
671 /// ```ignore
672 /// let sorted = query.order_by_float(Product::price());
673 /// ```
674 pub fn order_by_float(&self, path: KeyPaths<T, f64>) -> Vec<T> {
675 let mut results: Vec<T> = self
676 .data
677 .iter()
678 .filter(|item| self.filters.iter().all(|f| f(item)))
679 .cloned()
680 .collect();
681
682 results.sort_by(|a, b| {
683 let a_val = path.get(a).cloned().unwrap_or(0.0);
684 let b_val = path.get(b).cloned().unwrap_or(0.0);
685 a_val.partial_cmp(&b_val).unwrap_or(std::cmp::Ordering::Equal)
686 });
687 results
688 }
689
690 /// Orders results by a float field in descending order.
691 ///
692 /// **Note**: This method requires `T: Clone` as it creates owned sorted copies.
693 ///
694 /// # Arguments
695 ///
696 /// * `path` - The key-path to the f64 field to order by
697 ///
698 /// # Example
699 ///
700 /// ```ignore
701 /// let sorted = query.order_by_float_desc(Product::rating());
702 /// ```
703 pub fn order_by_float_desc(&self, path: KeyPaths<T, f64>) -> Vec<T> {
704 let mut results: Vec<T> = self
705 .data
706 .iter()
707 .filter(|item| self.filters.iter().all(|f| f(item)))
708 .cloned()
709 .collect();
710
711 results.sort_by(|a, b| {
712 let a_val = path.get(a).cloned().unwrap_or(0.0);
713 let b_val = path.get(b).cloned().unwrap_or(0.0);
714 b_val.partial_cmp(&a_val).unwrap_or(std::cmp::Ordering::Equal)
715 });
716 results
717 }
718
719 /// Groups results by a field value.
720 ///
721 /// **Note**: This method requires `T: Clone` as it creates owned copies in groups.
722 ///
723 /// # Arguments
724 ///
725 /// * `path` - The key-path to the field to group by
726 ///
727 /// # Example
728 ///
729 /// ```ignore
730 /// let by_category = query.group_by(Product::category());
731 /// ```
732 pub fn group_by<F>(&self, path: KeyPaths<T, F>) -> HashMap<F, Vec<T>>
733 where
734 F: Eq + std::hash::Hash + Clone + 'static,
735 {
736 let mut groups: HashMap<F, Vec<T>> = HashMap::new();
737
738 for item in self.data.iter() {
739 if self.filters.iter().all(|f| f(item)) {
740 if let Some(key) = path.get(item).cloned() {
741 groups.entry(key).or_insert_with(Vec::new).push(item.clone());
742 }
743 }
744 }
745
746 groups
747 }
748}
749
750/// Helper struct for pagination after a skip operation.
751///
752/// Created by calling `skip()` on a `Query`.
753pub struct QueryWithSkip<'a, 'b, T: 'static> {
754 query: &'b Query<'a, T>,
755 offset: usize,
756}
757
758impl<'a, 'b, T: 'static> QueryWithSkip<'a, 'b, T> {
759 /// Returns up to `n` items after skipping the offset.
760 ///
761 /// # Arguments
762 ///
763 /// * `n` - The maximum number of items to return
764 ///
765 /// # Example
766 ///
767 /// ```ignore
768 /// let page_2 = query.skip(20).limit(10);
769 /// ```
770 pub fn limit(&self, n: usize) -> Vec<&'a T> {
771 self.query
772 .data
773 .iter()
774 .filter(|item| self.query.filters.iter().all(|f| f(item)))
775 .skip(self.offset)
776 .take(n)
777 .collect()
778 }
779}
780