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