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_r(), |&price| price < 100.0)
26///     .order_by_float(Product::price_r());
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_r(), |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_r());
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_r());
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_r()).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_r());
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_r());
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_r());
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_r());
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_r(), &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_r(), &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_r(), &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_r(), &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_r(), &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_r(), &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_r(), &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_r(), 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_r(), 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_r(), 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_r());
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_r());
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_r());
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_r());
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_r());
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_r());
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_r());
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_r());
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