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