rust_queries_core/
ext.rs

1use crate::{Query, LazyQuery, Queryable};
2
3/// Extension trait that adds eager Query methods directly to slice-like containers
4/// 
5/// This trait allows you to call eager query methods directly on containers.
6/// For lazy queries, use the `QueryableExt::lazy_query()` method instead.
7/// 
8/// ```ignore
9/// use rust_queries_builder::{QueryExt, QueryableExt};
10/// 
11/// let products = vec![...];
12/// 
13/// // Eager query (collects immediately)
14/// let results = products.query().where_(...).all();
15/// 
16/// // Lazy query (deferred execution)
17/// let results = products.lazy_query().where_(...).collect();
18/// ```
19pub trait QueryExt<T> {
20    /// Create an eager Query from this container
21    /// 
22    /// This creates a Query that executes operations immediately when terminal
23    /// methods like `all()`, `first()`, or `count()` are called.
24    fn query(&self) -> Query<T>;
25}
26
27// Implementations for Vec
28impl<T: 'static> QueryExt<T> for Vec<T> {
29    fn query(&self) -> Query<T> {
30        Query::new(self)
31    }
32}
33
34// Implementations for slices
35impl<T: 'static> QueryExt<T> for [T] {
36    fn query(&self) -> Query<T> {
37        Query::new(self)
38    }
39}
40
41// Array implementations
42impl<T: 'static, const N: usize> QueryExt<T> for [T; N] {
43    fn query(&self) -> Query<T> {
44        Query::new(&self[..])
45    }
46}
47
48/// Extension trait for Queryable types that provides query building capabilities.
49/// 
50/// This trait extends any type implementing `Queryable<T>` with methods to create
51/// `LazyQuery` instances directly from the container's iterator.
52/// 
53/// # Example
54/// 
55/// ```ignore
56/// use rust_queries_builder::QueryableExt;
57/// 
58/// let map: HashMap<String, Product> = ...;
59/// let results: Vec<_> = map.lazy_query()
60///     .where_(Product::price(), |&p| p > 100.0)
61///     .collect();
62/// ```
63pub trait QueryableExt<T> {
64    /// Create a lazy query from this Queryable container
65    /// 
66    /// This returns a `LazyQuery` that can be used with all lazy query operations.
67    /// The operations are executed lazily when a terminal operation is called.
68    fn lazy_query(&self) -> LazyQuery<T, Box<dyn Iterator<Item = &T> + '_>>;
69}
70
71// Blanket implementation for all Queryable types
72impl<T: 'static, Q> QueryableExt<T> for Q
73where
74    Q: Queryable<T>,
75{
76    fn lazy_query(&self) -> LazyQuery<T, Box<dyn Iterator<Item = &T> + '_>> {
77        LazyQuery::from_iter(self.query_iter())
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84    use key_paths_derive::Keypath;
85
86    #[derive(Debug, Clone, PartialEq, Keypath)]
87    struct Product {
88        id: u32,
89        name: String,
90        price: f64,
91        category: String,
92    }
93
94    #[test]
95    fn test_vec_query_ext() {
96        let products = vec![
97            Product {
98                id: 1,
99                name: "Laptop".to_string(),
100                price: 999.99,
101                category: "Electronics".to_string(),
102            },
103            Product {
104                id: 2,
105                name: "Mouse".to_string(),
106                price: 29.99,
107                category: "Electronics".to_string(),
108            },
109        ];
110
111        let query = products
112            .query()
113            .where_(Product::price(), |&p| p > 50.0);
114        let results = query.all();
115
116        assert_eq!(results.len(), 1);
117        assert_eq!(results[0].name, "Laptop");
118    }
119
120    #[test]
121    fn test_vec_lazy_query_ext() {
122        let products = vec![
123            Product {
124                id: 1,
125                name: "Laptop".to_string(),
126                price: 999.99,
127                category: "Electronics".to_string(),
128            },
129            Product {
130                id: 2,
131                name: "Mouse".to_string(),
132                price: 29.99,
133                category: "Electronics".to_string(),
134            },
135        ];
136
137        let results: Vec<_> = products
138            .lazy_query()
139            .where_(Product::price(), |&p| p > 50.0)
140            .collect();
141
142        assert_eq!(results.len(), 1);
143        assert_eq!(results[0].name, "Laptop");
144    }
145
146    #[test]
147    fn test_array_query_ext() {
148        let products = [
149            Product {
150                id: 1,
151                name: "Laptop".to_string(),
152                price: 999.99,
153                category: "Electronics".to_string(),
154            },
155            Product {
156                id: 2,
157                name: "Mouse".to_string(),
158                price: 29.99,
159                category: "Electronics".to_string(),
160            },
161        ];
162
163        let results: Vec<_> = products
164            .lazy_query()
165            .where_(Product::price(), |&p| p < 100.0)
166            .collect();
167
168        assert_eq!(results.len(), 1);
169        assert_eq!(results[0].name, "Mouse");
170    }
171
172    #[test]
173    fn test_slice_query_ext() {
174        let products = vec![
175            Product {
176                id: 1,
177                name: "Laptop".to_string(),
178                price: 999.99,
179                category: "Electronics".to_string(),
180            },
181            Product {
182                id: 2,
183                name: "Mouse".to_string(),
184                price: 29.99,
185                category: "Electronics".to_string(),
186            },
187        ];
188
189        let slice = &products[..];
190        let query = slice
191            .query()
192            .where_(Product::category(), |cat| cat == "Electronics");
193        let results = query.count();
194
195        assert_eq!(results, 2);
196    }
197
198    #[test]
199    fn test_hashmap_queryable_ext() {
200        use std::collections::HashMap;
201
202        let mut map: HashMap<u32, Product> = HashMap::new();
203        map.insert(
204            1,
205            Product {
206                id: 1,
207                name: "Laptop".to_string(),
208                price: 999.99,
209                category: "Electronics".to_string(),
210            },
211        );
212        map.insert(
213            2,
214            Product {
215                id: 2,
216                name: "Mouse".to_string(),
217                price: 29.99,
218                category: "Electronics".to_string(),
219            },
220        );
221
222        // Using QueryableExt for HashMap
223        let results: Vec<_> = map
224            .lazy_query()
225            .where_(Product::price(), |&p| p > 50.0)
226            .collect();
227
228        assert_eq!(results.len(), 1);
229        assert_eq!(results[0].name, "Laptop");
230    }
231
232    #[test]
233    fn test_btreemap_queryable_ext() {
234        use std::collections::BTreeMap;
235
236        let mut map: BTreeMap<u32, Product> = BTreeMap::new();
237        map.insert(
238            1,
239            Product {
240                id: 1,
241                name: "Laptop".to_string(),
242                price: 999.99,
243                category: "Electronics".to_string(),
244            },
245        );
246        map.insert(
247            2,
248            Product {
249                id: 2,
250                name: "Mouse".to_string(),
251                price: 29.99,
252                category: "Electronics".to_string(),
253            },
254        );
255
256        // Using QueryableExt for BTreeMap
257        let count = map
258            .lazy_query()
259            .where_(Product::category(), |cat| cat == "Electronics")
260            .count();
261
262        assert_eq!(count, 2);
263    }
264
265    #[test]
266    fn test_vecdeque_queryable_ext() {
267        use std::collections::VecDeque;
268
269        let mut deque: VecDeque<Product> = VecDeque::new();
270        deque.push_back(Product {
271            id: 1,
272            name: "Laptop".to_string(),
273            price: 999.99,
274            category: "Electronics".to_string(),
275        });
276        deque.push_back(Product {
277            id: 2,
278            name: "Mouse".to_string(),
279            price: 29.99,
280            category: "Electronics".to_string(),
281        });
282
283        // Using QueryableExt for VecDeque
284        let results: Vec<_> = deque
285            .lazy_query()
286            .where_(Product::price(), |&p| p < 100.0)
287            .collect();
288
289        assert_eq!(results.len(), 1);
290        assert_eq!(results[0].name, "Mouse");
291    }
292
293    #[test]
294    fn test_linkedlist_queryable_ext() {
295        use std::collections::LinkedList;
296
297        let mut list: LinkedList<Product> = LinkedList::new();
298        list.push_back(Product {
299            id: 1,
300            name: "Laptop".to_string(),
301            price: 999.99,
302            category: "Electronics".to_string(),
303        });
304        list.push_back(Product {
305            id: 2,
306            name: "Mouse".to_string(),
307            price: 29.99,
308            category: "Electronics".to_string(),
309        });
310
311        // Using QueryableExt for LinkedList
312        let first = list
313            .lazy_query()
314            .where_(Product::price(), |&p| p > 900.0)
315            .first();
316
317        assert!(first.is_some());
318        assert_eq!(first.unwrap().name, "Laptop");
319    }
320
321    #[test]
322    fn test_queryable_ext_aggregations() {
323        use std::collections::VecDeque;
324
325        let mut deque: VecDeque<Product> = VecDeque::new();
326        deque.push_back(Product {
327            id: 1,
328            name: "Laptop".to_string(),
329            price: 999.99,
330            category: "Electronics".to_string(),
331        });
332        deque.push_back(Product {
333            id: 2,
334            name: "Mouse".to_string(),
335            price: 29.99,
336            category: "Electronics".to_string(),
337        });
338        deque.push_back(Product {
339            id: 3,
340            name: "Keyboard".to_string(),
341            price: 79.99,
342            category: "Electronics".to_string(),
343        });
344
345        // Test aggregation operations
346        let total = deque.lazy_query().sum_by(Product::price());
347        assert!((total - 1109.97).abs() < 0.01);
348
349        let avg = deque.lazy_query().avg_by(Product::price()).unwrap();
350        assert!((avg - 369.99).abs() < 0.01);
351
352        let min = deque.lazy_query().min_by_float(Product::price()).unwrap();
353        assert!((min - 29.99).abs() < 0.01);
354
355        let max = deque.lazy_query().max_by_float(Product::price()).unwrap();
356        assert!((max - 999.99).abs() < 0.01);
357    }
358
359    #[test]
360    fn test_queryable_ext_chaining() {
361        use std::collections::BTreeMap;
362
363        let mut map: BTreeMap<u32, Product> = BTreeMap::new();
364        map.insert(
365            1,
366            Product {
367                id: 1,
368                name: "Laptop".to_string(),
369                price: 999.99,
370                category: "Electronics".to_string(),
371            },
372        );
373        map.insert(
374            2,
375            Product {
376                id: 2,
377                name: "Mouse".to_string(),
378                price: 29.99,
379                category: "Electronics".to_string(),
380            },
381        );
382        map.insert(
383            3,
384            Product {
385                id: 3,
386                name: "Desk".to_string(),
387                price: 299.99,
388                category: "Furniture".to_string(),
389            },
390        );
391
392        // Test complex chaining with multiple where clauses
393        let results: Vec<_> = map
394            .lazy_query()
395            .where_(Product::category(), |cat| cat == "Electronics")
396            .where_(Product::price(), |&p| p < 500.0)
397            .skip_lazy(0)
398            .take_lazy(10)
399            .collect();
400
401        assert_eq!(results.len(), 1);
402        assert_eq!(results[0].name, "Mouse");
403    }
404}