rust_queries_core/
lock_lazy.rs

1//! Lazy query support for locked data structures.
2//!
3//! Provides lazy evaluation with early termination for locked collections.
4
5use crate::locks::LockValue;
6use key_paths_core::KeyPaths;
7use std::marker::PhantomData;
8
9/// Lazy query for locked data with early termination.
10pub struct LockLazyQuery<'a, T: 'static, L, I>
11where
12    L: LockValue<T> + 'a,
13    I: Iterator<Item = &'a L>,
14{
15    iter: I,
16    _phantom: PhantomData<&'a T>,
17}
18
19impl<'a, T: 'static, L, I> LockLazyQuery<'a, T, L, I>
20where
21    L: LockValue<T> + 'a,
22    I: Iterator<Item = &'a L> + 'a,
23{
24    /// Create a new lazy query from an iterator of locks.
25    pub fn new(iter: I) -> Self {
26        Self {
27            iter,
28            _phantom: PhantomData,
29        }
30    }
31
32    /// Filter using a key-path predicate (lazy).
33    pub fn where_<F, P>(self, path: KeyPaths<T, F>, predicate: P) -> LockLazyQuery<'a, T, L, impl Iterator<Item = &'a L> + 'a>
34    where
35        F: 'static,
36        P: Fn(&F) -> bool + 'a,
37    {
38        LockLazyQuery {
39            iter: self.iter.filter(move |lock| {
40                lock.with_value(|item| {
41                    path.get(item).map_or(false, |val| predicate(val))
42                })
43                .unwrap_or(false)
44            }),
45            _phantom: PhantomData,
46        }
47    }
48
49    /// Map to a field value (lazy).
50    /// 
51    /// This allows you to select only specific fields from locked data without
52    /// cloning the entire object. Perfect for projecting data efficiently.
53    /// 
54    /// # Example
55    /// 
56    /// ```ignore
57    /// // Select only product names (not full objects)
58    /// let names: Vec<String> = products
59    ///     .lock_lazy_query()
60    ///     .where_(Product::price_r(), |&p| p > 100.0)
61    ///     .select_lazy(Product::name_r())
62    ///     .collect();
63    /// 
64    /// // Select only IDs  
65    /// let ids: Vec<u32> = products
66    ///     .lock_lazy_query()
67    ///     .where_(Product::stock_r(), |&s| s > 0)
68    ///     .select_lazy(Product::id_r())
69    ///     .take(10)
70    ///     .collect();
71    /// 
72    /// // Select prices and compute sum
73    /// let total: f64 = products
74    ///     .lock_lazy_query()
75    ///     .where_(Product::category_r(), |c| c == "Electronics")
76    ///     .select_lazy(Product::price_r())
77    ///     .sum();
78    /// ```
79    /// 
80    /// **Performance Note**: This is much more efficient than collecting full objects
81    /// and then extracting fields, as it only clones the specific field value.
82    pub fn select_lazy<F>(self, path: KeyPaths<T, F>) -> impl Iterator<Item = F> + 'a
83    where
84        F: Clone + 'static,
85    {
86        self.iter.filter_map(move |lock| {
87            lock.with_value(|item| path.get(item).cloned()).flatten()
88        })
89    }
90
91    /// Take first N items (lazy).
92    pub fn take_lazy(self, n: usize) -> impl Iterator<Item = T> + 'a
93    where
94        T: Clone,
95    {
96        self.iter
97            .filter_map(|lock| lock.with_value(|item| item.clone()))
98            .take(n)
99    }
100
101    /// Skip first N items (lazy).
102    pub fn skip_lazy(self, n: usize) -> LockLazyQuery<'a, T, L, impl Iterator<Item = &'a L> + 'a> {
103        LockLazyQuery {
104            iter: self.iter.skip(n),
105            _phantom: PhantomData,
106        }
107    }
108
109    /// Count matching items (terminal).
110    pub fn count(self) -> usize {
111        self.iter.count()
112    }
113
114    /// Get first matching item (terminal).
115    pub fn first(mut self) -> Option<T>
116    where
117        T: Clone,
118    {
119        self.iter
120            .find_map(|lock| lock.with_value(|item| item.clone()))
121    }
122
123    /// Check if any items match (terminal).
124    pub fn any(mut self) -> bool {
125        self.iter.next().is_some()
126    }
127
128    /// Collect into Vec (terminal).
129    pub fn collect(self) -> Vec<T>
130    where
131        T: Clone,
132    {
133        self.iter
134            .filter_map(|lock| lock.with_value(|item| item.clone()))
135            .collect()
136    }
137
138    /// Get all matching items (alias for collect, similar to LockQuery::all).
139    /// 
140    /// This provides a familiar API for users coming from LockQuery.
141    /// 
142    /// # Example
143    /// 
144    /// ```ignore
145    /// let all_items: Vec<Product> = products
146    ///     .lock_lazy_query()
147    ///     .where_(Product::price_r(), |&p| p > 100.0)
148    ///     .all();
149    /// ```
150    pub fn all(self) -> Vec<T>
151    where
152        T: Clone,
153    {
154        self.collect()
155    }
156
157    // ========================================================================
158    // AGGREGATION FUNCTIONS
159    // ========================================================================
160
161    /// Sum a numeric field (terminal).
162    /// 
163    /// Efficiently computes the sum without collecting all items into a Vec first.
164    /// 
165    /// # Example
166    /// 
167    /// ```ignore
168    /// // Sum all prices
169    /// let total_value: f64 = products
170    ///     .lock_lazy_query()
171    ///     .where_(Product::stock_r(), |&s| s > 0)
172    ///     .sum(Product::price_r());
173    /// 
174    /// // Sum stock quantities
175    /// let total_stock: u32 = products
176    ///     .lock_lazy_query()
177    ///     .where_(Product::category_r(), |c| c == "Electronics")
178    ///     .sum(Product::stock_r());
179    /// ```
180    pub fn sum<F>(self, path: KeyPaths<T, F>) -> F
181    where
182        F: Clone + std::ops::Add<Output = F> + Default + 'static,
183    {
184        self.iter
185            .filter_map(|lock| {
186                lock.with_value(|item| path.get(item).cloned()).flatten()
187            })
188            .fold(F::default(), |acc, val| acc + val)
189    }
190
191    /// Calculate average of f64 field (terminal).
192    /// 
193    /// Returns None if no items match.
194    /// 
195    /// # Example
196    /// 
197    /// ```ignore
198    /// let avg_price = products
199    ///     .lock_lazy_query()
200    ///     .where_(Product::stock_r(), |&s| s > 0)
201    ///     .avg(Product::price_r());
202    /// 
203    /// match avg_price {
204    ///     Some(avg) => println!("Average price: ${:.2}", avg),
205    ///     None => println!("No items found"),
206    /// }
207    /// ```
208    pub fn avg(self, path: KeyPaths<T, f64>) -> Option<f64> {
209        let values: Vec<f64> = self.iter
210            .filter_map(|lock| {
211                lock.with_value(|item| path.get(item).cloned()).flatten()
212            })
213            .collect();
214        
215        if values.is_empty() {
216            None
217        } else {
218            Some(values.iter().sum::<f64>() / values.len() as f64)
219        }
220    }
221
222    /// Find minimum value (terminal).
223    /// 
224    /// Returns None if no items match.
225    /// 
226    /// # Example
227    /// 
228    /// ```ignore
229    /// let min_stock = products
230    ///     .lock_lazy_query()
231    ///     .where_(Product::stock_r(), |&s| s > 0)
232    ///     .min(Product::stock_r());
233    /// 
234    /// println!("Minimum stock level: {:?}", min_stock);
235    /// ```
236    pub fn min<F>(self, path: KeyPaths<T, F>) -> Option<F>
237    where
238        F: Ord + Clone + 'static,
239    {
240        self.iter
241            .filter_map(|lock| {
242                lock.with_value(|item| path.get(item).cloned()).flatten()
243            })
244            .min()
245    }
246
247    /// Find maximum value (terminal).
248    /// 
249    /// Returns None if no items match.
250    /// 
251    /// # Example
252    /// 
253    /// ```ignore
254    /// let max_price = products
255    ///     .lock_lazy_query()
256    ///     .where_(Product::category_r(), |c| c == "Electronics")
257    ///     .max(Product::price_r());
258    /// 
259    /// println!("Most expensive: ${:.2}", max_price.unwrap_or(0.0));
260    /// ```
261    pub fn max<F>(self, path: KeyPaths<T, F>) -> Option<F>
262    where
263        F: Ord + Clone + 'static,
264    {
265        self.iter
266            .filter_map(|lock| {
267                lock.with_value(|item| path.get(item).cloned()).flatten()
268            })
269            .max()
270    }
271
272    /// Find minimum float value (terminal).
273    /// 
274    /// Handles f64 values correctly with partial ordering.
275    /// 
276    /// # Example
277    /// 
278    /// ```ignore
279    /// let cheapest = products
280    ///     .lock_lazy_query()
281    ///     .where_(Product::stock_r(), |&s| s > 0)
282    ///     .min_float(Product::price_r());
283    /// ```
284    pub fn min_float(self, path: KeyPaths<T, f64>) -> Option<f64> {
285        self.iter
286            .filter_map(|lock| {
287                lock.with_value(|item| path.get(item).cloned()).flatten()
288            })
289            .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
290    }
291
292    /// Find maximum float value (terminal).
293    /// 
294    /// Handles f64 values correctly with partial ordering.
295    /// 
296    /// # Example
297    /// 
298    /// ```ignore
299    /// let most_expensive = products
300    ///     .lock_lazy_query()
301    ///     .where_(Product::stock_r(), |&s| s > 0)
302    ///     .max_float(Product::price_r());
303    /// ```
304    pub fn max_float(self, path: KeyPaths<T, f64>) -> Option<f64> {
305        self.iter
306            .filter_map(|lock| {
307                lock.with_value(|item| path.get(item).cloned()).flatten()
308            })
309            .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
310    }
311
312    // ========================================================================
313    // SQL-LIKE FUNCTIONS
314    // ========================================================================
315
316    /// Check if any items exist matching the criteria (terminal).
317    /// 
318    /// Alias for `any()`, provides SQL-like EXISTS semantics.
319    /// Stops at the first match - very efficient!
320    /// 
321    /// # Example
322    /// 
323    /// ```ignore
324    /// let has_expensive = products
325    ///     .lock_lazy_query()
326    ///     .where_(Product::price_r(), |&p| p > 1000.0)
327    ///     .exists();
328    /// 
329    /// if has_expensive {
330    ///     println!("We have luxury items!");
331    /// }
332    /// 
333    /// // SQL equivalent: SELECT EXISTS(SELECT 1 FROM products WHERE price > 1000)
334    /// ```
335    pub fn exists(self) -> bool {
336        self.any()
337    }
338
339    /// Limit results to first N items (lazy).
340    /// 
341    /// Alias for creating a limited iterator. Use with `.collect()` or `.all()`.
342    /// 
343    /// # Example
344    /// 
345    /// ```ignore
346    /// let top_5: Vec<Product> = products
347    ///     .lock_lazy_query()
348    ///     .where_(Product::stock_r(), |&s| s > 10)
349    ///     .limit(5)
350    ///     .collect();
351    /// 
352    /// // SQL equivalent: SELECT * FROM products WHERE stock > 10 LIMIT 5
353    /// ```
354    pub fn limit(self, n: usize) -> impl Iterator<Item = T> + 'a
355    where
356        T: Clone,
357    {
358        self.iter
359            .filter_map(|lock| lock.with_value(|item| item.clone()))
360            .take(n)
361    }
362
363    /// Skip first N items (lazy).
364    /// 
365    /// Alias for `skip_lazy()` with better SQL-like naming.
366    /// 
367    /// # Example
368    /// 
369    /// ```ignore
370    /// // Get second page (skip 10, take 10)
371    /// let page_2 = products
372    ///     .lock_lazy_query()
373    ///     .skip(10)
374    ///     .limit(10)
375    ///     .collect();
376    /// 
377    /// // SQL equivalent: SELECT * FROM products LIMIT 10 OFFSET 10
378    /// ```
379    pub fn skip(self, n: usize) -> LockLazyQuery<'a, T, L, impl Iterator<Item = &'a L> + 'a> {
380        self.skip_lazy(n)
381    }
382
383    /// Get distinct values for a field (terminal).
384    /// 
385    /// Returns a Vec of unique field values. Uses HashSet internally.
386    /// 
387    /// # Example
388    /// 
389    /// ```ignore
390    /// let categories: Vec<String> = products
391    ///     .lock_lazy_query()
392    ///     .where_(Product::stock_r(), |&s| s > 0)
393    ///     .distinct(Product::category_r());
394    /// 
395    /// println!("Available categories: {:?}", categories);
396    /// 
397    /// // SQL equivalent: SELECT DISTINCT category FROM products WHERE stock > 0
398    /// ```
399    pub fn distinct<F>(self, path: KeyPaths<T, F>) -> Vec<F>
400    where
401        F: Eq + std::hash::Hash + Clone + 'static,
402    {
403        use std::collections::HashSet;
404        
405        let set: HashSet<F> = self.iter
406            .filter_map(|lock| {
407                lock.with_value(|item| path.get(item).cloned()).flatten()
408            })
409            .collect();
410        
411        set.into_iter().collect()
412    }
413
414    /// Get last matching item (terminal).
415    /// 
416    /// **Note**: This consumes the entire iterator to find the last item.
417    /// Less efficient than `first()` for lazy evaluation.
418    /// 
419    /// # Example
420    /// 
421    /// ```ignore
422    /// let last_product = products
423    ///     .lock_lazy_query()
424    ///     .where_(Product::stock_r(), |&s| s > 0)
425    ///     .last();
426    /// ```
427    pub fn last(self) -> Option<T>
428    where
429        T: Clone,
430    {
431        self.iter
432            .filter_map(|lock| lock.with_value(|item| item.clone()))
433            .last()
434    }
435
436    /// Find item at specific index (terminal).
437    /// 
438    /// Returns None if index is out of bounds.
439    /// 
440    /// # Example
441    /// 
442    /// ```ignore
443    /// let third_item = products
444    ///     .lock_lazy_query()
445    ///     .where_(Product::stock_r(), |&s| s > 0)
446    ///     .nth(2);  // 0-indexed, so this is the 3rd item
447    /// ```
448    pub fn nth(mut self, n: usize) -> Option<T>
449    where
450        T: Clone,
451    {
452        self.iter
453            .nth(n)
454            .and_then(|lock| lock.with_value(|item| item.clone()))
455    }
456
457    /// Check if all items match a predicate (terminal).
458    /// 
459    /// Returns true if all items match, false otherwise.
460    /// Short-circuits on first non-match.
461    /// 
462    /// # Example
463    /// 
464    /// ```ignore
465    /// let all_in_stock = products
466    ///     .lock_lazy_query()
467    ///     .where_(Product::category_r(), |c| c == "Electronics")
468    ///     .all_match(Product::stock_r(), |&s| s > 0);
469    /// 
470    /// if all_in_stock {
471    ///     println!("All electronics are in stock!");
472    /// }
473    /// ```
474    pub fn all_match<F, P>(mut self, path: KeyPaths<T, F>, predicate: P) -> bool
475    where
476        F: 'static,
477        P: Fn(&F) -> bool + 'a,
478    {
479        self.iter.all(|lock| {
480            lock.with_value(|item| {
481                path.get(item).map_or(false, |val| predicate(val))
482            })
483            .unwrap_or(false)
484        })
485    }
486
487    /// Find first item matching an additional predicate (terminal).
488    /// 
489    /// Like `first()` but with an extra condition.
490    /// 
491    /// # Example
492    /// 
493    /// ```ignore
494    /// let expensive_laptop = products
495    ///     .lock_lazy_query()
496    ///     .where_(Product::category_r(), |c| c == "Electronics")
497    ///     .find(Product::price_r(), |&p| p > 500.0);
498    /// ```
499    pub fn find<F, P>(mut self, path: KeyPaths<T, F>, predicate: P) -> Option<T>
500    where
501        F: 'static,
502        P: Fn(&F) -> bool + 'a,
503        T: Clone,
504    {
505        self.iter.find_map(|lock| {
506            lock.with_value(|item| {
507                if path.get(item).map_or(false, |val| predicate(val)) {
508                    Some(item.clone())
509                } else {
510                    None
511                }
512            })
513            .flatten()
514        })
515    }
516
517    /// Count items matching an additional condition (terminal).
518    /// 
519    /// Like `count()` but with a field-specific predicate.
520    /// 
521    /// # Example
522    /// 
523    /// ```ignore
524    /// let expensive_count = products
525    ///     .lock_lazy_query()
526    ///     .where_(Product::category_r(), |c| c == "Electronics")
527    ///     .count_where(Product::price_r(), |&p| p > 500.0);
528    /// ```
529    pub fn count_where<F, P>(self, path: KeyPaths<T, F>, predicate: P) -> usize
530    where
531        F: 'static,
532        P: Fn(&F) -> bool + 'a,
533    {
534        self.iter.filter(|lock| {
535            lock.with_value(|item| {
536                path.get(item).map_or(false, |val| predicate(val))
537            })
538            .unwrap_or(false)
539        }).count()
540    }
541}
542
543