rust_queries_builder/
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;
8
9/// A query builder for filtering, selecting, ordering, grouping, and aggregating data.
10///
11/// # Type Parameters
12///
13/// * `'a` - The lifetime of the data being queried
14/// * `T` - The type of items in the collection
15///
16/// # Example
17///
18/// ```ignore
19/// let products = vec![/* ... */];
20/// let query = Query::new(&products)
21///     .where_(Product::price_r(), |&price| price < 100.0)
22///     .order_by_float(Product::price_r());
23/// ```
24pub struct Query<'a, T: 'static> {
25    data: &'a [T],
26    filters: Vec<Box<dyn Fn(&T) -> bool>>,
27}
28
29// Core implementation without Clone requirement
30impl<'a, T: 'static> Query<'a, T> {
31    /// Creates a new query from a slice of data.
32    ///
33    /// # Arguments
34    ///
35    /// * `data` - A slice of items to query
36    ///
37    /// # Example
38    ///
39    /// ```ignore
40    /// let query = Query::new(&products);
41    /// ```
42    pub fn new(data: &'a [T]) -> Self {
43        Self {
44            data,
45            filters: Vec::new(),
46        }
47    }
48
49    /// Adds a filter predicate using a key-path.
50    ///
51    /// # Arguments
52    ///
53    /// * `path` - The key-path to the field to filter on
54    /// * `predicate` - A function that returns true for items to keep
55    ///
56    /// # Example
57    ///
58    /// ```ignore
59    /// let query = Query::new(&products)
60    ///     .where_(Product::category_r(), |cat| cat == "Electronics");
61    /// ```
62    pub fn where_<F>(mut self, path: KeyPaths<T, F>, predicate: impl Fn(&F) -> bool + 'static) -> Self
63    where
64        F: 'static,
65    {
66        self.filters.push(Box::new(move |item| {
67            path.get(item).map_or(false, |val| predicate(val))
68        }));
69        self
70    }
71
72    /// Returns all items matching the query filters.
73    ///
74    /// # Example
75    ///
76    /// ```ignore
77    /// let results = query.all();
78    /// ```
79    pub fn all(&self) -> Vec<&T> {
80        self.data
81            .iter()
82            .filter(|item| self.filters.iter().all(|f| f(item)))
83            .collect()
84    }
85
86    /// Returns the first item matching the query filters.
87    ///
88    /// # Example
89    ///
90    /// ```ignore
91    /// let first = query.first();
92    /// ```
93    pub fn first(&self) -> Option<&T> {
94        self.data
95            .iter()
96            .find(|item| self.filters.iter().all(|f| f(item)))
97    }
98
99    /// Returns the count of items matching the query filters.
100    ///
101    /// # Example
102    ///
103    /// ```ignore
104    /// let count = query.count();
105    /// ```
106    pub fn count(&self) -> usize {
107        self.data
108            .iter()
109            .filter(|item| self.filters.iter().all(|f| f(item)))
110            .count()
111    }
112
113    /// Returns the first `n` items matching the query filters.
114    ///
115    /// # Arguments
116    ///
117    /// * `n` - The maximum number of items to return
118    ///
119    /// # Example
120    ///
121    /// ```ignore
122    /// let first_10 = query.limit(10);
123    /// ```
124    pub fn limit(&self, n: usize) -> Vec<&T> {
125        self.data
126            .iter()
127            .filter(|item| self.filters.iter().all(|f| f(item)))
128            .take(n)
129            .collect()
130    }
131
132    /// Skips the first `offset` items for pagination.
133    ///
134    /// # Arguments
135    ///
136    /// * `offset` - The number of items to skip
137    ///
138    /// # Example
139    ///
140    /// ```ignore
141    /// let page_2 = query.skip(20).limit(10);
142    /// ```
143    pub fn skip<'b>(&'b self, offset: usize) -> QueryWithSkip<'a, 'b, T> {
144        QueryWithSkip {
145            query: self,
146            offset,
147        }
148    }
149
150    /// Projects/selects a single field from results.
151    ///
152    /// # Arguments
153    ///
154    /// * `path` - The key-path to the field to select
155    ///
156    /// # Example
157    ///
158    /// ```ignore
159    /// let names = query.select(Product::name_r());
160    /// ```
161    pub fn select<F>(&self, path: KeyPaths<T, F>) -> Vec<F>
162    where
163        F: Clone + 'static,
164    {
165        self.data
166            .iter()
167            .filter(|item| self.filters.iter().all(|f| f(item)))
168            .filter_map(|item| path.get(item).cloned())
169            .collect()
170    }
171
172    /// Computes the sum of a numeric field.
173    ///
174    /// # Arguments
175    ///
176    /// * `path` - The key-path to the numeric field
177    ///
178    /// # Example
179    ///
180    /// ```ignore
181    /// let total_price = query.sum(Product::price_r());
182    /// ```
183    pub fn sum<F>(&self, path: KeyPaths<T, F>) -> F
184    where
185        F: Clone + std::ops::Add<Output = F> + Default + 'static,
186    {
187        self.data
188            .iter()
189            .filter(|item| self.filters.iter().all(|f| f(item)))
190            .filter_map(|item| path.get(item).cloned())
191            .fold(F::default(), |acc, val| acc + val)
192    }
193
194    /// Computes the average of a float field.
195    ///
196    /// # Arguments
197    ///
198    /// * `path` - The key-path to the f64 field
199    ///
200    /// # Example
201    ///
202    /// ```ignore
203    /// let avg_price = query.avg(Product::price_r()).unwrap_or(0.0);
204    /// ```
205    pub fn avg(&self, path: KeyPaths<T, f64>) -> Option<f64> {
206        let items: Vec<f64> = self
207            .data
208            .iter()
209            .filter(|item| self.filters.iter().all(|f| f(item)))
210            .filter_map(|item| path.get(item).cloned())
211            .collect();
212
213        if items.is_empty() {
214            None
215        } else {
216            Some(items.iter().sum::<f64>() / items.len() as f64)
217        }
218    }
219
220    /// Finds the minimum value of a field.
221    ///
222    /// # Arguments
223    ///
224    /// * `path` - The key-path to the field
225    ///
226    /// # Example
227    ///
228    /// ```ignore
229    /// let min_stock = query.min(Product::stock_r());
230    /// ```
231    pub fn min<F>(&self, path: KeyPaths<T, F>) -> Option<F>
232    where
233        F: Ord + Clone + 'static,
234    {
235        self.data
236            .iter()
237            .filter(|item| self.filters.iter().all(|f| f(item)))
238            .filter_map(|item| path.get(item).cloned())
239            .min()
240    }
241
242    /// Finds the maximum value of a field.
243    ///
244    /// # Arguments
245    ///
246    /// * `path` - The key-path to the field
247    ///
248    /// # Example
249    ///
250    /// ```ignore
251    /// let max_stock = query.max(Product::stock_r());
252    /// ```
253    pub fn max<F>(&self, path: KeyPaths<T, F>) -> Option<F>
254    where
255        F: Ord + Clone + 'static,
256    {
257        self.data
258            .iter()
259            .filter(|item| self.filters.iter().all(|f| f(item)))
260            .filter_map(|item| path.get(item).cloned())
261            .max()
262    }
263
264    /// Finds the minimum value of a float field.
265    ///
266    /// # Arguments
267    ///
268    /// * `path` - The key-path to the f64 field
269    ///
270    /// # Example
271    ///
272    /// ```ignore
273    /// let min_price = query.min_float(Product::price_r());
274    /// ```
275    pub fn min_float(&self, path: KeyPaths<T, f64>) -> Option<f64> {
276        self.data
277            .iter()
278            .filter(|item| self.filters.iter().all(|f| f(item)))
279            .filter_map(|item| path.get(item).cloned())
280            .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
281    }
282
283    /// Finds the maximum value of a float field.
284    ///
285    /// # Arguments
286    ///
287    /// * `path` - The key-path to the f64 field
288    ///
289    /// # Example
290    ///
291    /// ```ignore
292    /// let max_price = query.max_float(Product::price_r());
293    /// ```
294    pub fn max_float(&self, path: KeyPaths<T, f64>) -> Option<f64> {
295        self.data
296            .iter()
297            .filter(|item| self.filters.iter().all(|f| f(item)))
298            .filter_map(|item| path.get(item).cloned())
299            .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
300    }
301
302    /// Checks if any items match the query filters.
303    ///
304    /// # Example
305    ///
306    /// ```ignore
307    /// let has_results = query.exists();
308    /// ```
309    pub fn exists(&self) -> bool {
310        self.data
311            .iter()
312            .any(|item| self.filters.iter().all(|f| f(item)))
313    }
314}
315
316// Operations that require Clone - separated for flexibility
317impl<'a, T: 'static + Clone> Query<'a, T> {
318    /// Orders results by a field in ascending order.
319    /// 
320    /// **Note**: This method requires `T: Clone` as it creates owned sorted copies.
321    ///
322    /// # Arguments
323    ///
324    /// * `path` - The key-path to the field to order by
325    ///
326    /// # Example
327    ///
328    /// ```ignore
329    /// let sorted = query.order_by(Product::name_r());
330    /// ```
331    pub fn order_by<F>(&self, path: KeyPaths<T, F>) -> Vec<T>
332    where
333        F: Ord + Clone + 'static,
334    {
335        let mut results: Vec<T> = self
336            .data
337            .iter()
338            .filter(|item| self.filters.iter().all(|f| f(item)))
339            .cloned()
340            .collect();
341
342        results.sort_by_key(|item| path.get(item).cloned());
343        results
344    }
345
346    /// Orders results by a field in descending order.
347    /// 
348    /// **Note**: This method requires `T: Clone` as it creates owned sorted copies.
349    ///
350    /// # Arguments
351    ///
352    /// * `path` - The key-path to the field to order by
353    ///
354    /// # Example
355    ///
356    /// ```ignore
357    /// let sorted = query.order_by_desc(Product::stock_r());
358    /// ```
359    pub fn order_by_desc<F>(&self, path: KeyPaths<T, F>) -> Vec<T>
360    where
361        F: Ord + Clone + 'static,
362    {
363        let mut results: Vec<T> = self
364            .data
365            .iter()
366            .filter(|item| self.filters.iter().all(|f| f(item)))
367            .cloned()
368            .collect();
369
370        results.sort_by(|a, b| {
371            let a_val = path.get(a).cloned();
372            let b_val = path.get(b).cloned();
373            b_val.cmp(&a_val)
374        });
375        results
376    }
377
378    /// Orders results by a float field in ascending order.
379    /// 
380    /// **Note**: This method requires `T: Clone` as it creates owned sorted copies.
381    ///
382    /// # Arguments
383    ///
384    /// * `path` - The key-path to the f64 field to order by
385    ///
386    /// # Example
387    ///
388    /// ```ignore
389    /// let sorted = query.order_by_float(Product::price_r());
390    /// ```
391    pub fn order_by_float(&self, path: KeyPaths<T, f64>) -> Vec<T> {
392        let mut results: Vec<T> = self
393            .data
394            .iter()
395            .filter(|item| self.filters.iter().all(|f| f(item)))
396            .cloned()
397            .collect();
398
399        results.sort_by(|a, b| {
400            let a_val = path.get(a).cloned().unwrap_or(0.0);
401            let b_val = path.get(b).cloned().unwrap_or(0.0);
402            a_val.partial_cmp(&b_val).unwrap_or(std::cmp::Ordering::Equal)
403        });
404        results
405    }
406
407    /// Orders results by a float field in descending order.
408    /// 
409    /// **Note**: This method requires `T: Clone` as it creates owned sorted copies.
410    ///
411    /// # Arguments
412    ///
413    /// * `path` - The key-path to the f64 field to order by
414    ///
415    /// # Example
416    ///
417    /// ```ignore
418    /// let sorted = query.order_by_float_desc(Product::rating_r());
419    /// ```
420    pub fn order_by_float_desc(&self, path: KeyPaths<T, f64>) -> Vec<T> {
421        let mut results: Vec<T> = self
422            .data
423            .iter()
424            .filter(|item| self.filters.iter().all(|f| f(item)))
425            .cloned()
426            .collect();
427
428        results.sort_by(|a, b| {
429            let a_val = path.get(a).cloned().unwrap_or(0.0);
430            let b_val = path.get(b).cloned().unwrap_or(0.0);
431            b_val.partial_cmp(&a_val).unwrap_or(std::cmp::Ordering::Equal)
432        });
433        results
434    }
435
436    /// Groups results by a field value.
437    /// 
438    /// **Note**: This method requires `T: Clone` as it creates owned copies in groups.
439    ///
440    /// # Arguments
441    ///
442    /// * `path` - The key-path to the field to group by
443    ///
444    /// # Example
445    ///
446    /// ```ignore
447    /// let by_category = query.group_by(Product::category_r());
448    /// ```
449    pub fn group_by<F>(&self, path: KeyPaths<T, F>) -> HashMap<F, Vec<T>>
450    where
451        F: Eq + std::hash::Hash + Clone + 'static,
452    {
453        let mut groups: HashMap<F, Vec<T>> = HashMap::new();
454
455        for item in self.data.iter() {
456            if self.filters.iter().all(|f| f(item)) {
457                if let Some(key) = path.get(item).cloned() {
458                    groups.entry(key).or_insert_with(Vec::new).push(item.clone());
459                }
460            }
461        }
462
463        groups
464    }
465}
466
467/// Helper struct for pagination after a skip operation.
468///
469/// Created by calling `skip()` on a `Query`.
470pub struct QueryWithSkip<'a, 'b, T: 'static> {
471    query: &'b Query<'a, T>,
472    offset: usize,
473}
474
475impl<'a, 'b, T: 'static> QueryWithSkip<'a, 'b, T> {
476    /// Returns up to `n` items after skipping the offset.
477    ///
478    /// # Arguments
479    ///
480    /// * `n` - The maximum number of items to return
481    ///
482    /// # Example
483    ///
484    /// ```ignore
485    /// let page_2 = query.skip(20).limit(10);
486    /// ```
487    pub fn limit(&self, n: usize) -> Vec<&'a T> {
488        self.query
489            .data
490            .iter()
491            .filter(|item| self.query.filters.iter().all(|f| f(item)))
492            .skip(self.offset)
493            .take(n)
494            .collect()
495    }
496}
497