rust_queries_core/
lock_query.rs

1//! Full SQL-like query support for locked data structures.
2//!
3//! This module provides a complete Query API for collections of locked values,
4//! enabling WHERE, SELECT, ORDER BY, GROUP BY, aggregations, and JOIN operations
5//! without copying data unnecessarily.
6//!
7//! # Example
8//!
9//! ```ignore
10//! use rust_queries_core::{LockQuery};
11//! use std::sync::{Arc, RwLock};
12//! use std::collections::HashMap;
13//!
14//! let products: HashMap<String, Arc<RwLock<Product>>> = /* ... */;
15//!
16//! // Full SQL-like syntax on locked data!
17//! let expensive = LockQuery::new(&products)
18//!     .where_(Product::category(), |cat| cat == "Electronics")
19//!     .where_(Product::price(), |&p| p > 500.0)
20//!     .order_by_float(Product::rating())
21//!     .limit(10);
22//! ```
23
24use crate::locks::LockValue;
25use key_paths_core::KeyPaths;
26use std::collections::HashMap;
27use std::sync::{Arc, RwLock, Mutex};
28
29/// A query builder for locked data structures.
30///
31/// Provides full SQL-like query operations (WHERE, SELECT, ORDER BY, GROUP BY)
32/// on collections of locked values without unnecessary copying.
33pub struct LockQuery<'a, T: 'static, L>
34where
35    L: LockValue<T> + 'a,
36{
37    locks: Vec<&'a L>,
38    filters: Vec<Box<dyn Fn(&T) -> bool + 'a>>,
39    _phantom: std::marker::PhantomData<T>,
40}
41
42impl<'a, T: 'static, L> LockQuery<'a, T, L>
43where
44    L: LockValue<T> + 'a,
45{
46    /// Create a new lock query from a collection of locks.
47    ///
48    /// # Example
49    ///
50    /// ```ignore
51    /// let query = LockQuery::from_locks(product_map.values().collect());
52    /// ```
53    pub fn from_locks(locks: Vec<&'a L>) -> Self {
54        Self {
55            locks,
56            filters: Vec::new(),
57            _phantom: std::marker::PhantomData,
58        }
59    }
60
61    /// Add a WHERE clause using a key-path.
62    ///
63    /// # Example
64    ///
65    /// ```ignore
66    /// let query = LockQuery::new(&products)
67    ///     .where_(Product::category(), |cat| cat == "Electronics");
68    /// ```
69    pub fn where_<F>(mut self, path: KeyPaths<T, F>, predicate: impl Fn(&F) -> bool + 'a) -> Self
70    where
71        F: 'static,
72    {
73        self.filters.push(Box::new(move |item| {
74            path.get(item).map_or(false, |val| predicate(val))
75        }));
76        self
77    }
78
79    /// Get all matching items (collects by cloning).
80    ///
81    /// # Example
82    ///
83    /// ```ignore
84    /// let results: Vec<Product> = query.all();
85    /// ```
86    pub fn all(&self) -> Vec<T>
87    where
88        T: Clone,
89    {
90        self.locks
91            .iter()
92            .filter_map(|lock| {
93                lock.with_value(|item| {
94                    if self.filters.iter().all(|f| f(item)) {
95                        Some(item.clone())
96                    } else {
97                        None
98                    }
99                })
100                .flatten()
101            })
102            .collect()
103    }
104
105    /// Get the first matching item.
106    ///
107    /// # Example
108    ///
109    /// ```ignore
110    /// let first = query.first();
111    /// ```
112    pub fn first(&self) -> Option<T>
113    where
114        T: Clone,
115    {
116        self.locks
117            .iter()
118            .find_map(|lock| {
119                lock.with_value(|item| {
120                    if self.filters.iter().all(|f| f(item)) {
121                        Some(item.clone())
122                    } else {
123                        None
124                    }
125                })
126                .flatten()
127            })
128    }
129
130    /// Count matching items.
131    ///
132    /// # Example
133    ///
134    /// ```ignore
135    /// let count = query.count();
136    /// ```
137    pub fn count(&self) -> usize {
138        self.locks
139            .iter()
140            .filter(|lock| {
141                lock.with_value(|item| self.filters.iter().all(|f| f(item)))
142                    .unwrap_or(false)
143            })
144            .count()
145    }
146
147    /// Check if any items match.
148    ///
149    /// # Example
150    ///
151    /// ```ignore
152    /// let exists = query.exists();
153    /// ```
154    pub fn exists(&self) -> bool {
155        self.locks
156            .iter()
157            .any(|lock| {
158                lock.with_value(|item| self.filters.iter().all(|f| f(item)))
159                    .unwrap_or(false)
160            })
161    }
162
163    /// Limit results to first N items.
164    ///
165    /// # Example
166    ///
167    /// ```ignore
168    /// let first_10 = query.limit(10);
169    /// ```
170    pub fn limit(&self, n: usize) -> Vec<T>
171    where
172        T: Clone,
173    {
174        self.locks
175            .iter()
176            .filter_map(|lock| {
177                lock.with_value(|item| {
178                    if self.filters.iter().all(|f| f(item)) {
179                        Some(item.clone())
180                    } else {
181                        None
182                    }
183                })
184                .flatten()
185            })
186            .take(n)
187            .collect()
188    }
189
190    /// Select/project a field.
191    ///
192    /// # Example
193    ///
194    /// ```ignore
195    /// let names: Vec<String> = query.select(Product::name());
196    /// ```
197    pub fn select<F>(&self, path: KeyPaths<T, F>) -> Vec<F>
198    where
199        F: Clone + 'static,
200    {
201        self.locks
202            .iter()
203            .filter_map(|lock| {
204                lock.with_value(|item| {
205                    if self.filters.iter().all(|f| f(item)) {
206                        path.get(item).cloned()
207                    } else {
208                        None
209                    }
210                })
211                .flatten()
212            })
213            .collect()
214    }
215
216    /// Sum a numeric field.
217    ///
218    /// # Example
219    ///
220    /// ```ignore
221    /// let total = query.sum(Product::price());
222    /// ```
223    pub fn sum<F>(&self, path: KeyPaths<T, F>) -> F
224    where
225        F: Clone + std::ops::Add<Output = F> + Default + 'static,
226    {
227        self.locks
228            .iter()
229            .filter_map(|lock| {
230                lock.with_value(|item| {
231                    if self.filters.iter().all(|f| f(item)) {
232                        path.get(item).cloned()
233                    } else {
234                        None
235                    }
236                })
237                .flatten()
238            })
239            .fold(F::default(), |acc, val| acc + val)
240    }
241
242    /// Calculate average of f64 field.
243    ///
244    /// # Example
245    ///
246    /// ```ignore
247    /// let avg = query.avg(Product::price());
248    /// ```
249    pub fn avg(&self, path: KeyPaths<T, f64>) -> Option<f64> {
250        let values: Vec<f64> = self.select(path);
251        if values.is_empty() {
252            None
253        } else {
254            Some(values.iter().sum::<f64>() / values.len() as f64)
255        }
256    }
257
258    /// Find minimum value.
259    ///
260    /// # Example
261    ///
262    /// ```ignore
263    /// let min = query.min(Product::stock());
264    /// ```
265    pub fn min<F>(&self, path: KeyPaths<T, F>) -> Option<F>
266    where
267        F: Ord + Clone + 'static,
268    {
269        self.select(path).into_iter().min()
270    }
271
272    /// Find maximum value.
273    ///
274    /// # Example
275    ///
276    /// ```ignore
277    /// let max = query.max(Product::stock());
278    /// ```
279    pub fn max<F>(&self, path: KeyPaths<T, F>) -> Option<F>
280    where
281        F: Ord + Clone + 'static,
282    {
283        self.select(path).into_iter().max()
284    }
285
286    /// Find minimum float value.
287    pub fn min_float(&self, path: KeyPaths<T, f64>) -> Option<f64> {
288        self.select(path)
289            .into_iter()
290            .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
291    }
292
293    /// Find maximum float value.
294    pub fn max_float(&self, path: KeyPaths<T, f64>) -> Option<f64> {
295        self.select(path)
296            .into_iter()
297            .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
298    }
299
300    /// Order by a field (requires collecting data).
301    ///
302    /// # Example
303    ///
304    /// ```ignore
305    /// let sorted = query.order_by(Product::name());
306    /// ```
307    pub fn order_by<F>(&self, path: KeyPaths<T, F>) -> Vec<T>
308    where
309        F: Ord + Clone + 'static,
310        T: Clone,
311    {
312        let mut results = self.all();
313        results.sort_by_key(|item| path.get(item).cloned());
314        results
315    }
316
317    /// Order by a field descending.
318    pub fn order_by_desc<F>(&self, path: KeyPaths<T, F>) -> Vec<T>
319    where
320        F: Ord + Clone + 'static,
321        T: Clone,
322    {
323        let mut results = self.all();
324        results.sort_by(|a, b| {
325            let a_val = path.get(a).cloned();
326            let b_val = path.get(b).cloned();
327            b_val.cmp(&a_val)
328        });
329        results
330    }
331
332    /// Order by float field.
333    pub fn order_by_float(&self, path: KeyPaths<T, f64>) -> Vec<T>
334    where
335        T: Clone,
336    {
337        let mut results = self.all();
338        results.sort_by(|a, b| {
339            let a_val = path.get(a).cloned().unwrap_or(0.0);
340            let b_val = path.get(b).cloned().unwrap_or(0.0);
341            a_val.partial_cmp(&b_val).unwrap_or(std::cmp::Ordering::Equal)
342        });
343        results
344    }
345
346    /// Order by float field descending.
347    pub fn order_by_float_desc(&self, path: KeyPaths<T, f64>) -> Vec<T>
348    where
349        T: Clone,
350    {
351        let mut results = self.all();
352        results.sort_by(|a, b| {
353            let a_val = path.get(a).cloned().unwrap_or(0.0);
354            let b_val = path.get(b).cloned().unwrap_or(0.0);
355            b_val.partial_cmp(&a_val).unwrap_or(std::cmp::Ordering::Equal)
356        });
357        results
358    }
359
360    /// Group by a field.
361    ///
362    /// # Example
363    ///
364    /// ```ignore
365    /// let groups = query.group_by(Product::category());
366    /// ```
367    pub fn group_by<F>(&self, path: KeyPaths<T, F>) -> HashMap<F, Vec<T>>
368    where
369        F: Eq + std::hash::Hash + Clone + 'static,
370        T: Clone,
371    {
372        let mut groups: HashMap<F, Vec<T>> = HashMap::new();
373
374        for lock in &self.locks {
375            if let Some(item) = lock.with_value(|item| {
376                if self.filters.iter().all(|f| f(item)) {
377                    Some(item.clone())
378                } else {
379                    None
380                }
381            })
382            .flatten()
383            {
384                if let Some(key) = path.get(&item).cloned() {
385                    groups.entry(key).or_insert_with(Vec::new).push(item);
386                }
387            }
388        }
389
390        groups
391    }
392
393    // i64 DateTime Aggregators (Unix timestamps in milliseconds)
394    /// Finds minimum i64 timestamp value.
395    ///
396    /// # Example
397    ///
398    /// ```ignore
399    /// let earliest = query.min_timestamp(Event::created_at());
400    /// ```
401    pub fn min_timestamp(&self, path: KeyPaths<T, i64>) -> Option<i64> {
402        self.locks
403            .iter()
404            .filter_map(|lock| {
405                lock.with_value(|item| {
406                    if self.filters.iter().all(|f| f(item)) {
407                        path.get(item).cloned()
408                    } else {
409                        None
410                    }
411                })
412                .flatten()
413            })
414            .min()
415    }
416
417    /// Finds maximum i64 timestamp value.
418    ///
419    /// # Example
420    ///
421    /// ```ignore
422    /// let latest = query.max_timestamp(Event::created_at());
423    /// ```
424    pub fn max_timestamp(&self, path: KeyPaths<T, i64>) -> Option<i64> {
425        self.locks
426            .iter()
427            .filter_map(|lock| {
428                lock.with_value(|item| {
429                    if self.filters.iter().all(|f| f(item)) {
430                        path.get(item).cloned()
431                    } else {
432                        None
433                    }
434                })
435                .flatten()
436            })
437            .max()
438    }
439
440    /// Computes average of i64 timestamp values.
441    ///
442    /// # Example
443    ///
444    /// ```ignore
445    /// let avg = query.avg_timestamp(Event::created_at());
446    /// ```
447    pub fn avg_timestamp(&self, path: KeyPaths<T, i64>) -> Option<i64> {
448        let items: Vec<i64> = self.locks
449            .iter()
450            .filter_map(|lock| {
451                lock.with_value(|item| {
452                    if self.filters.iter().all(|f| f(item)) {
453                        path.get(item).cloned()
454                    } else {
455                        None
456                    }
457                })
458                .flatten()
459            })
460            .collect();
461
462        if items.is_empty() {
463            None
464        } else {
465            Some(items.iter().sum::<i64>() / items.len() as i64)
466        }
467    }
468
469    /// Computes sum of i64 timestamp values.
470    ///
471    /// # Example
472    ///
473    /// ```ignore
474    /// let total = query.sum_timestamp(Event::created_at());
475    /// ```
476    pub fn sum_timestamp(&self, path: KeyPaths<T, i64>) -> i64 {
477        self.locks
478            .iter()
479            .filter_map(|lock| {
480                lock.with_value(|item| {
481                    if self.filters.iter().all(|f| f(item)) {
482                        path.get(item).cloned()
483                    } else {
484                        None
485                    }
486                })
487                .flatten()
488            })
489            .sum()
490    }
491
492    /// Counts i64 timestamp values.
493    ///
494    /// # Example
495    ///
496    /// ```ignore
497    /// let count = query.count_timestamp(Event::created_at());
498    /// ```
499    pub fn count_timestamp(&self, path: KeyPaths<T, i64>) -> usize {
500        self.locks
501            .iter()
502            .filter(|lock| {
503                lock.with_value(|item| {
504                    if self.filters.iter().all(|f| f(item)) {
505                        path.get(item).is_some()
506                    } else {
507                        false
508                    }
509                })
510                .unwrap_or(false)
511            })
512            .count()
513    }
514
515    /// Filter by i64 timestamp being after a reference time.
516    ///
517    /// # Arguments
518    ///
519    /// * `path` - The key-path to the i64 timestamp field
520    /// * `reference` - The reference timestamp to compare against
521    ///
522    /// # Example
523    ///
524    /// ```ignore
525    /// let recent = query.where_after_timestamp(Event::created_at(), cutoff_time);
526    /// ```
527    pub fn where_after_timestamp(self, path: KeyPaths<T, i64>, reference: i64) -> Self {
528        self.where_(path, move |timestamp| timestamp > &reference)
529    }
530
531    /// Filter by i64 timestamp being before a reference time.
532    ///
533    /// # Arguments
534    ///
535    /// * `path` - The key-path to the i64 timestamp field
536    /// * `reference` - The reference timestamp to compare against
537    ///
538    /// # Example
539    ///
540    /// ```ignore
541    /// let old = query.where_before_timestamp(Event::created_at(), cutoff_time);
542    /// ```
543    pub fn where_before_timestamp(self, path: KeyPaths<T, i64>, reference: i64) -> Self {
544        self.where_(path, move |timestamp| timestamp < &reference)
545    }
546
547    /// Filter by i64 timestamp being between two times (inclusive).
548    ///
549    /// # Arguments
550    ///
551    /// * `path` - The key-path to the i64 timestamp field
552    /// * `start` - The start timestamp
553    /// * `end` - The end timestamp
554    ///
555    /// # Example
556    ///
557    /// ```ignore
558    /// let range = query.where_between_timestamp(Event::created_at(), start, end);
559    /// ```
560    pub fn where_between_timestamp(self, path: KeyPaths<T, i64>, start: i64, end: i64) -> Self {
561        self.where_(path, move |timestamp| timestamp >= &start && timestamp <= &end)
562    }
563
564    /// Filter by i64 timestamp being within the last N days.
565    ///
566    /// # Arguments
567    ///
568    /// * `path` - The key-path to the i64 timestamp field
569    /// * `days` - Number of days to look back
570    ///
571    /// # Example
572    ///
573    /// ```ignore
574    /// let recent = query.where_last_days_timestamp(Event::created_at(), 30);
575    /// ```
576    pub fn where_last_days_timestamp(self, path: KeyPaths<T, i64>, days: i64) -> Self {
577        let now = chrono::Utc::now().timestamp_millis();
578        let cutoff = now - (days * 24 * 60 * 60 * 1000); // Convert days to milliseconds
579        self.where_after_timestamp(path, cutoff)
580    }
581
582    /// Filter by i64 timestamp being within the next N days.
583    ///
584    /// # Arguments
585    ///
586    /// * `path` - The key-path to the i64 timestamp field
587    /// * `days` - Number of days to look forward
588    ///
589    /// # Example
590    ///
591    /// ```ignore
592    /// let upcoming = query.where_next_days_timestamp(Event::scheduled_at(), 7);
593    /// ```
594    pub fn where_next_days_timestamp(self, path: KeyPaths<T, i64>, days: i64) -> Self {
595        let now = chrono::Utc::now().timestamp_millis();
596        let cutoff = now + (days * 24 * 60 * 60 * 1000); // Convert days to milliseconds
597        self.where_before_timestamp(path, cutoff)
598    }
599
600    /// Filter by i64 timestamp being within the last N hours.
601    ///
602    /// # Arguments
603    ///
604    /// * `path` - The key-path to the i64 timestamp field
605    /// * `hours` - Number of hours to look back
606    ///
607    /// # Example
608    ///
609    /// ```ignore
610    /// let recent = query.where_last_hours_timestamp(Event::created_at(), 24);
611    /// ```
612    pub fn where_last_hours_timestamp(self, path: KeyPaths<T, i64>, hours: i64) -> Self {
613        let now = chrono::Utc::now().timestamp_millis();
614        let cutoff = now - (hours * 60 * 60 * 1000); // Convert hours to milliseconds
615        self.where_after_timestamp(path, cutoff)
616    }
617
618    /// Filter by i64 timestamp being within the next N hours.
619    ///
620    /// # Arguments
621    ///
622    /// * `path` - The key-path to the i64 timestamp field
623    /// * `hours` - Number of hours to look forward
624    ///
625    /// # Example
626    ///
627    /// ```ignore
628    /// let upcoming = query.where_next_hours_timestamp(Event::scheduled_at(), 2);
629    /// ```
630    pub fn where_next_hours_timestamp(self, path: KeyPaths<T, i64>, hours: i64) -> Self {
631        let now = chrono::Utc::now().timestamp_millis();
632        let cutoff = now + (hours * 60 * 60 * 1000); // Convert hours to milliseconds
633        self.where_before_timestamp(path, cutoff)
634    }
635
636    /// Filter by i64 timestamp being within the last N minutes.
637    ///
638    /// # Arguments
639    ///
640    /// * `path` - The key-path to the i64 timestamp field
641    /// * `minutes` - Number of minutes to look back
642    ///
643    /// # Example
644    ///
645    /// ```ignore
646    /// let recent = query.where_last_minutes_timestamp(Event::created_at(), 60);
647    /// ```
648    pub fn where_last_minutes_timestamp(self, path: KeyPaths<T, i64>, minutes: i64) -> Self {
649        let now = chrono::Utc::now().timestamp_millis();
650        let cutoff = now - (minutes * 60 * 1000); // Convert minutes to milliseconds
651        self.where_after_timestamp(path, cutoff)
652    }
653
654    /// Filter by i64 timestamp being within the next N minutes.
655    ///
656    /// # Arguments
657    ///
658    /// * `path` - The key-path to the i64 timestamp field
659    /// * `minutes` - Number of minutes to look forward
660    ///
661    /// # Example
662    ///
663    /// ```ignore
664    /// let upcoming = query.where_next_minutes_timestamp(Event::scheduled_at(), 30);
665    /// ```
666    pub fn where_next_minutes_timestamp(self, path: KeyPaths<T, i64>, minutes: i64) -> Self {
667        let now = chrono::Utc::now().timestamp_millis();
668        let cutoff = now + (minutes * 60 * 1000); // Convert minutes to milliseconds
669        self.where_before_timestamp(path, cutoff)
670    }
671}
672
673/// Helper to create LockQuery from HashMap.
674pub trait LockQueryable<T, L>
675where
676    L: LockValue<T>,
677{
678    /// Create a LockQuery for SQL-like operations.
679    fn lock_query(&self) -> LockQuery<'_, T, L>;
680}
681
682// Implementation for HashMap<K, Arc<RwLock<V>>>
683impl<K, V> LockQueryable<V, Arc<RwLock<V>>> for HashMap<K, Arc<RwLock<V>>>
684where
685    K: Eq + std::hash::Hash,
686{
687    fn lock_query(&self) -> LockQuery<'_, V, Arc<RwLock<V>>> {
688        LockQuery::from_locks(self.values().collect())
689    }
690}
691
692// Implementation for HashMap<K, Arc<Mutex<V>>>
693impl<K, V> LockQueryable<V, Arc<Mutex<V>>> for HashMap<K, Arc<Mutex<V>>>
694where
695    K: Eq + std::hash::Hash,
696{
697    fn lock_query(&self) -> LockQuery<'_, V, Arc<Mutex<V>>> {
698        LockQuery::from_locks(self.values().collect())
699    }
700}
701
702// Implementation for Vec<Arc<RwLock<T>>>
703impl<T> LockQueryable<T, Arc<RwLock<T>>> for Vec<Arc<RwLock<T>>> {
704    fn lock_query(&self) -> LockQuery<'_, T, Arc<RwLock<T>>> {
705        LockQuery::from_locks(self.iter().collect())
706    }
707}
708
709// Implementation for Vec<Arc<Mutex<T>>>
710impl<T> LockQueryable<T, Arc<Mutex<T>>> for Vec<Arc<Mutex<T>>> {
711    fn lock_query(&self) -> LockQuery<'_, T, Arc<Mutex<T>>> {
712        LockQuery::from_locks(self.iter().collect())
713    }
714}
715
716// Extension trait for creating lazy lock queries
717use crate::lock_lazy::LockLazyQuery;
718
719/// Extension trait for creating lazy lock queries.
720pub trait LockLazyQueryable<T, L>
721where
722    L: LockValue<T>,
723{
724    /// Create a lazy lock query.
725    fn lock_lazy_query(&self) -> LockLazyQuery<'_, T, L, impl Iterator<Item = &L>>;
726}
727
728// Implementation for HashMap<K, Arc<RwLock<V>>>
729impl<K, V> LockLazyQueryable<V, Arc<RwLock<V>>> for HashMap<K, Arc<RwLock<V>>>
730where
731    K: Eq + std::hash::Hash,
732{
733    fn lock_lazy_query(&self) -> LockLazyQuery<'_, V, Arc<RwLock<V>>, impl Iterator<Item = &Arc<RwLock<V>>>> {
734        LockLazyQuery::new(self.values())
735    }
736}
737
738// Implementation for HashMap<K, Arc<Mutex<V>>>
739impl<K, V> LockLazyQueryable<V, Arc<Mutex<V>>> for HashMap<K, Arc<Mutex<V>>>
740where
741    K: Eq + std::hash::Hash,
742{
743    fn lock_lazy_query(&self) -> LockLazyQuery<'_, V, Arc<Mutex<V>>, impl Iterator<Item = &Arc<Mutex<V>>>> {
744        LockLazyQuery::new(self.values())
745    }
746}
747
748// Implementation for Vec<Arc<RwLock<T>>>
749impl<T> LockLazyQueryable<T, Arc<RwLock<T>>> for Vec<Arc<RwLock<T>>> {
750    fn lock_lazy_query(&self) -> LockLazyQuery<'_, T, Arc<RwLock<T>>, impl Iterator<Item = &Arc<RwLock<T>>>> {
751        LockLazyQuery::new(self.iter())
752    }
753}
754
755// Implementation for Vec<Arc<Mutex<T>>>
756impl<T> LockLazyQueryable<T, Arc<Mutex<T>>> for Vec<Arc<Mutex<T>>> {
757    fn lock_lazy_query(&self) -> LockLazyQuery<'_, T, Arc<Mutex<T>>, impl Iterator<Item = &Arc<Mutex<T>>>> {
758        LockLazyQuery::new(self.iter())
759    }
760}
761
762#[cfg(test)]
763mod tests {
764    use super::*;
765    use std::sync::{Arc, RwLock};
766    use key_paths_derive::Keypath;
767
768    #[derive(Clone, Keypath)]
769    struct Product {
770        id: u32,
771        name: String,
772        price: f64,
773        category: String,
774    }
775
776    fn create_test_map() -> HashMap<String, Arc<RwLock<Product>>> {
777        let mut map = HashMap::new();
778        map.insert(
779            "p1".to_string(),
780            Arc::new(RwLock::new(Product {
781                id: 1,
782                name: "Laptop".to_string(),
783                price: 999.99,
784                category: "Electronics".to_string(),
785            })),
786        );
787        map.insert(
788            "p2".to_string(),
789            Arc::new(RwLock::new(Product {
790                id: 2,
791                name: "Chair".to_string(),
792                price: 299.99,
793                category: "Furniture".to_string(),
794            })),
795        );
796        map.insert(
797            "p3".to_string(),
798            Arc::new(RwLock::new(Product {
799                id: 3,
800                name: "Mouse".to_string(),
801                price: 29.99,
802                category: "Electronics".to_string(),
803            })),
804        );
805        map
806    }
807
808    #[test]
809    fn test_lock_query_where() {
810        let map = create_test_map();
811        let query = map.lock_query();
812        let count = query
813            .where_(Product::category(), |cat| cat == "Electronics")
814            .count();
815        assert_eq!(count, 2);
816    }
817
818    #[test]
819    fn test_lock_query_select() {
820        let map = create_test_map();
821        let names = map
822            .lock_query()
823            .select(Product::name());
824        assert_eq!(names.len(), 3);
825    }
826
827    #[test]
828    fn test_lock_query_sum() {
829        let map = create_test_map();
830        let total = map
831            .lock_query()
832            .sum(Product::price());
833        assert!((total - 1329.97).abs() < 0.01);
834    }
835
836    #[test]
837    fn test_lock_query_group_by() {
838        let map = create_test_map();
839        let groups = map
840            .lock_query()
841            .group_by(Product::category());
842        assert_eq!(groups.len(), 2);
843        assert_eq!(groups.get("Electronics").unwrap().len(), 2);
844    }
845
846    #[test]
847    fn test_lock_query_order_by() {
848        let map = create_test_map();
849        let sorted = map
850            .lock_query()
851            .order_by_float(Product::price());
852        assert_eq!(sorted[0].price, 29.99);
853        assert_eq!(sorted[2].price, 999.99);
854    }
855}
856