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    // i64 DateTime Aggregators (Unix timestamps in milliseconds)
751    // ============================================================================
752
753    /// Finds the minimum i64 timestamp value.
754    ///
755    /// # Arguments
756    ///
757    /// * `path` - The key-path to the i64 timestamp field
758    ///
759    /// # Example
760    ///
761    /// ```ignore
762    /// let earliest = query.min_timestamp(Event::created_at());
763    /// ```
764    #[cfg(feature = "datetime")]
765    pub fn min_timestamp(&self, path: KeyPaths<T, i64>) -> Option<i64> {
766        self.data
767            .iter()
768            .filter(|item| self.filters.iter().all(|f| f(item)))
769            .filter_map(|item| path.get(item).cloned())
770            .min()
771    }
772
773    /// Finds the maximum i64 timestamp value.
774    ///
775    /// # Arguments
776    ///
777    /// * `path` - The key-path to the i64 timestamp field
778    ///
779    /// # Example
780    ///
781    /// ```ignore
782    /// let latest = query.max_timestamp(Event::created_at());
783    /// ```
784    #[cfg(feature = "datetime")]
785    pub fn max_timestamp(&self, path: KeyPaths<T, i64>) -> Option<i64> {
786        self.data
787            .iter()
788            .filter(|item| self.filters.iter().all(|f| f(item)))
789            .filter_map(|item| path.get(item).cloned())
790            .max()
791    }
792
793    /// Calculates the average of i64 timestamp values.
794    ///
795    /// # Arguments
796    ///
797    /// * `path` - The key-path to the i64 timestamp field
798    ///
799    /// # Example
800    ///
801    /// ```ignore
802    /// let avg_timestamp = query.avg_timestamp(Event::created_at()).unwrap_or(0);
803    /// ```
804    #[cfg(feature = "datetime")]
805    pub fn avg_timestamp(&self, path: KeyPaths<T, i64>) -> Option<i64> {
806        let items: Vec<i64> = self
807            .data
808            .iter()
809            .filter(|item| self.filters.iter().all(|f| f(item)))
810            .filter_map(|item| path.get(item).cloned())
811            .collect();
812
813        if items.is_empty() {
814            None
815        } else {
816            Some(items.iter().sum::<i64>() / items.len() as i64)
817        }
818    }
819
820    /// Calculates the sum of i64 timestamp values.
821    ///
822    /// # Arguments
823    ///
824    /// * `path` - The key-path to the i64 timestamp field
825    ///
826    /// # Example
827    ///
828    /// ```ignore
829    /// let total_timestamp = query.sum_timestamp(Event::created_at());
830    /// ```
831    #[cfg(feature = "datetime")]
832    pub fn sum_timestamp(&self, path: KeyPaths<T, i64>) -> i64 {
833        self.data
834            .iter()
835            .filter(|item| self.filters.iter().all(|f| f(item)))
836            .filter_map(|item| path.get(item).cloned())
837            .sum()
838    }
839
840    /// Counts the number of non-null i64 timestamp values.
841    ///
842    /// # Arguments
843    ///
844    /// * `path` - The key-path to the i64 timestamp field
845    ///
846    /// # Example
847    ///
848    /// ```ignore
849    /// let timestamp_count = query.count_timestamp(Event::created_at());
850    /// ```
851    #[cfg(feature = "datetime")]
852    pub fn count_timestamp(&self, path: KeyPaths<T, i64>) -> usize {
853        self.data
854            .iter()
855            .filter(|item| self.filters.iter().all(|f| f(item)))
856            .filter(|item| path.get(item).is_some())
857            .count()
858    }
859
860    /// Filters by i64 timestamp being after a reference timestamp.
861    ///
862    /// # Arguments
863    ///
864    /// * `path` - The key-path to the i64 timestamp field
865    /// * `reference` - The reference timestamp to compare against
866    ///
867    /// # Example
868    ///
869    /// ```ignore
870    /// let recent = query.where_after_timestamp(Event::created_at(), cutoff_timestamp);
871    /// ```
872    #[cfg(feature = "datetime")]
873    pub fn where_after_timestamp(self, path: KeyPaths<T, i64>, reference: i64) -> Self {
874        self.where_(path, move |timestamp| timestamp > &reference)
875    }
876
877    /// Filters by i64 timestamp being before a reference timestamp.
878    ///
879    /// # Arguments
880    ///
881    /// * `path` - The key-path to the i64 timestamp field
882    /// * `reference` - The reference timestamp to compare against
883    ///
884    /// # Example
885    ///
886    /// ```ignore
887    /// let old = query.where_before_timestamp(Event::created_at(), cutoff_timestamp);
888    /// ```
889    #[cfg(feature = "datetime")]
890    pub fn where_before_timestamp(self, path: KeyPaths<T, i64>, reference: i64) -> Self {
891        self.where_(path, move |timestamp| timestamp < &reference)
892    }
893
894    /// Filters by i64 timestamp being between two timestamps (inclusive).
895    ///
896    /// # Arguments
897    ///
898    /// * `path` - The key-path to the i64 timestamp field
899    /// * `start` - The start timestamp
900    /// * `end` - The end timestamp
901    ///
902    /// # Example
903    ///
904    /// ```ignore
905    /// let range = query.where_between_timestamp(Event::created_at(), start_ts, end_ts);
906    /// ```
907    #[cfg(feature = "datetime")]
908    pub fn where_between_timestamp(self, path: KeyPaths<T, i64>, start: i64, end: i64) -> Self {
909        self.where_(path, move |timestamp| timestamp >= &start && timestamp <= &end)
910    }
911
912    /// Filters by i64 timestamp being within the last N days from now.
913    ///
914    /// # Arguments
915    ///
916    /// * `path` - The key-path to the i64 timestamp field
917    /// * `days` - Number of days to look back
918    ///
919    /// # Example
920    ///
921    /// ```ignore
922    /// let recent = query.where_last_days_timestamp(Event::created_at(), 30);
923    /// ```
924    #[cfg(feature = "datetime")]
925    pub fn where_last_days_timestamp(self, path: KeyPaths<T, i64>, days: i64) -> Self {
926        let now = chrono::Utc::now().timestamp_millis();
927        let cutoff = now - (days * 24 * 60 * 60 * 1000); // Convert days to milliseconds
928        self.where_after_timestamp(path, cutoff)
929    }
930
931    /// Filters by i64 timestamp being within the next N days from now.
932    ///
933    /// # Arguments
934    ///
935    /// * `path` - The key-path to the i64 timestamp field
936    /// * `days` - Number of days to look ahead
937    ///
938    /// # Example
939    ///
940    /// ```ignore
941    /// let upcoming = query.where_next_days_timestamp(Event::scheduled_at(), 7);
942    /// ```
943    #[cfg(feature = "datetime")]
944    pub fn where_next_days_timestamp(self, path: KeyPaths<T, i64>, days: i64) -> Self {
945        let now = chrono::Utc::now().timestamp_millis();
946        let cutoff = now + (days * 24 * 60 * 60 * 1000); // Convert days to milliseconds
947        self.where_before_timestamp(path, cutoff)
948    }
949
950    /// Filters by i64 timestamp being within the last N hours from now.
951    ///
952    /// # Arguments
953    ///
954    /// * `path` - The key-path to the i64 timestamp field
955    /// * `hours` - Number of hours to look back
956    ///
957    /// # Example
958    ///
959    /// ```ignore
960    /// let recent = query.where_last_hours_timestamp(Event::created_at(), 24);
961    /// ```
962    #[cfg(feature = "datetime")]
963    pub fn where_last_hours_timestamp(self, path: KeyPaths<T, i64>, hours: i64) -> Self {
964        let now = chrono::Utc::now().timestamp_millis();
965        let cutoff = now - (hours * 60 * 60 * 1000); // Convert hours to milliseconds
966        self.where_after_timestamp(path, cutoff)
967    }
968
969    /// Filters by i64 timestamp being within the next N hours from now.
970    ///
971    /// # Arguments
972    ///
973    /// * `path` - The key-path to the i64 timestamp field
974    /// * `hours` - Number of hours to look ahead
975    ///
976    /// # Example
977    ///
978    /// ```ignore
979    /// let upcoming = query.where_next_hours_timestamp(Event::scheduled_at(), 2);
980    /// ```
981    #[cfg(feature = "datetime")]
982    pub fn where_next_hours_timestamp(self, path: KeyPaths<T, i64>, hours: i64) -> Self {
983        let now = chrono::Utc::now().timestamp_millis();
984        let cutoff = now + (hours * 60 * 60 * 1000); // Convert hours to milliseconds
985        self.where_before_timestamp(path, cutoff)
986    }
987
988    /// Filters by i64 timestamp being within the last N minutes from now.
989    ///
990    /// # Arguments
991    ///
992    /// * `path` - The key-path to the i64 timestamp field
993    /// * `minutes` - Number of minutes to look back
994    ///
995    /// # Example
996    ///
997    /// ```ignore
998    /// let recent = query.where_last_minutes_timestamp(Event::created_at(), 60);
999    /// ```
1000    #[cfg(feature = "datetime")]
1001    pub fn where_last_minutes_timestamp(self, path: KeyPaths<T, i64>, minutes: i64) -> Self {
1002        let now = chrono::Utc::now().timestamp_millis();
1003        let cutoff = now - (minutes * 60 * 1000); // Convert minutes to milliseconds
1004        self.where_after_timestamp(path, cutoff)
1005    }
1006
1007    /// Filters by i64 timestamp being within the next N minutes from now.
1008    ///
1009    /// # Arguments
1010    ///
1011    /// * `path` - The key-path to the i64 timestamp field
1012    /// * `minutes` - Number of minutes to look ahead
1013    ///
1014    /// # Example
1015    ///
1016    /// ```ignore
1017    /// let upcoming = query.where_next_minutes_timestamp(Event::scheduled_at(), 30);
1018    /// ```
1019    #[cfg(feature = "datetime")]
1020    pub fn where_next_minutes_timestamp(self, path: KeyPaths<T, i64>, minutes: i64) -> Self {
1021        let now = chrono::Utc::now().timestamp_millis();
1022        let cutoff = now + (minutes * 60 * 1000); // Convert minutes to milliseconds
1023        self.where_before_timestamp(path, cutoff)
1024    }
1025
1026    /// Orders results by i64 timestamp in ascending order (oldest first).
1027    ///
1028    /// # Arguments
1029    ///
1030    /// * `path` - The key-path to the i64 timestamp field
1031    ///
1032    /// # Example
1033    ///
1034    /// ```ignore
1035    /// let sorted = query.order_by_timestamp(Event::created_at());
1036    /// ```
1037    #[cfg(feature = "datetime")]
1038    pub fn order_by_timestamp(&self, path: KeyPaths<T, i64>) -> Vec<T> {
1039        let mut results: Vec<T> = self
1040            .data
1041            .iter()
1042            .filter(|item| self.filters.iter().all(|f| f(item)))
1043            .cloned()
1044            .collect();
1045
1046        results.sort_by(|a, b| {
1047            let a_val = path.get(a).cloned().unwrap_or(0);
1048            let b_val = path.get(b).cloned().unwrap_or(0);
1049            a_val.cmp(&b_val)
1050        });
1051        results
1052    }
1053
1054    /// Orders results by i64 timestamp in descending order (newest first).
1055    ///
1056    /// # Arguments
1057    ///
1058    /// * `path` - The key-path to the i64 timestamp field
1059    ///
1060    /// # Example
1061    ///
1062    /// ```ignore
1063    /// let sorted = query.order_by_timestamp_desc(Event::created_at());
1064    /// ```
1065    #[cfg(feature = "datetime")]
1066    pub fn order_by_timestamp_desc(&self, path: KeyPaths<T, i64>) -> Vec<T> {
1067        let mut results: Vec<T> = self
1068            .data
1069            .iter()
1070            .filter(|item| self.filters.iter().all(|f| f(item)))
1071            .cloned()
1072            .collect();
1073
1074        results.sort_by(|a, b| {
1075            let a_val = path.get(a).cloned().unwrap_or(0);
1076            let b_val = path.get(b).cloned().unwrap_or(0);
1077            b_val.cmp(&a_val)
1078        });
1079        results
1080    }
1081}
1082
1083/// Helper struct for pagination after a skip operation.
1084///
1085/// Created by calling `skip()` on a `Query`.
1086pub struct QueryWithSkip<'a, 'b, T: 'static> {
1087    query: &'b Query<'a, T>,
1088    offset: usize,
1089}
1090
1091impl<'a, 'b, T: 'static> QueryWithSkip<'a, 'b, T> {
1092    /// Returns up to `n` items after skipping the offset.
1093    ///
1094    /// # Arguments
1095    ///
1096    /// * `n` - The maximum number of items to return
1097    ///
1098    /// # Example
1099    ///
1100    /// ```ignore
1101    /// let page_2 = query.skip(20).limit(10);
1102    /// ```
1103    pub fn limit(&self, n: usize) -> Vec<&'a T> {
1104        self.query
1105            .data
1106            .iter()
1107            .filter(|item| self.query.filters.iter().all(|f| f(item)))
1108            .skip(self.offset)
1109            .take(n)
1110            .collect()
1111    }
1112}
1113