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