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;
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