use crate::locks::LockValue;
use key_paths_core::KeyPaths;
use std::collections::HashMap;
use std::sync::{Arc, RwLock, Mutex};
pub struct LockQuery<'a, T: 'static, L>
where
L: LockValue<T> + 'a,
{
locks: Vec<&'a L>,
filters: Vec<Box<dyn Fn(&T) -> bool + 'a>>,
_phantom: std::marker::PhantomData<T>,
}
impl<'a, T: 'static, L> LockQuery<'a, T, L>
where
L: LockValue<T> + 'a,
{
pub fn from_locks(locks: Vec<&'a L>) -> Self {
Self {
locks,
filters: Vec::new(),
_phantom: std::marker::PhantomData,
}
}
pub fn where_<F>(mut self, path: KeyPaths<T, F>, predicate: impl Fn(&F) -> bool + 'a) -> Self
where
F: 'static,
{
self.filters.push(Box::new(move |item| {
path.get(item).map_or(false, |val| predicate(val))
}));
self
}
pub fn all(&self) -> Vec<T>
where
T: Clone,
{
self.locks
.iter()
.filter_map(|lock| {
lock.with_value(|item| {
if self.filters.iter().all(|f| f(item)) {
Some(item.clone())
} else {
None
}
})
.flatten()
})
.collect()
}
pub fn first(&self) -> Option<T>
where
T: Clone,
{
self.locks
.iter()
.find_map(|lock| {
lock.with_value(|item| {
if self.filters.iter().all(|f| f(item)) {
Some(item.clone())
} else {
None
}
})
.flatten()
})
}
pub fn count(&self) -> usize {
self.locks
.iter()
.filter(|lock| {
lock.with_value(|item| self.filters.iter().all(|f| f(item)))
.unwrap_or(false)
})
.count()
}
pub fn exists(&self) -> bool {
self.locks
.iter()
.any(|lock| {
lock.with_value(|item| self.filters.iter().all(|f| f(item)))
.unwrap_or(false)
})
}
pub fn limit(&self, n: usize) -> Vec<T>
where
T: Clone,
{
self.locks
.iter()
.filter_map(|lock| {
lock.with_value(|item| {
if self.filters.iter().all(|f| f(item)) {
Some(item.clone())
} else {
None
}
})
.flatten()
})
.take(n)
.collect()
}
pub fn select<F>(&self, path: KeyPaths<T, F>) -> Vec<F>
where
F: Clone + 'static,
{
self.locks
.iter()
.filter_map(|lock| {
lock.with_value(|item| {
if self.filters.iter().all(|f| f(item)) {
path.get(item).cloned()
} else {
None
}
})
.flatten()
})
.collect()
}
pub fn sum<F>(&self, path: KeyPaths<T, F>) -> F
where
F: Clone + std::ops::Add<Output = F> + Default + 'static,
{
self.locks
.iter()
.filter_map(|lock| {
lock.with_value(|item| {
if self.filters.iter().all(|f| f(item)) {
path.get(item).cloned()
} else {
None
}
})
.flatten()
})
.fold(F::default(), |acc, val| acc + val)
}
pub fn avg(&self, path: KeyPaths<T, f64>) -> Option<f64> {
let values: Vec<f64> = self.select(path);
if values.is_empty() {
None
} else {
Some(values.iter().sum::<f64>() / values.len() as f64)
}
}
pub fn min<F>(&self, path: KeyPaths<T, F>) -> Option<F>
where
F: Ord + Clone + 'static,
{
self.select(path).into_iter().min()
}
pub fn max<F>(&self, path: KeyPaths<T, F>) -> Option<F>
where
F: Ord + Clone + 'static,
{
self.select(path).into_iter().max()
}
pub fn min_float(&self, path: KeyPaths<T, f64>) -> Option<f64> {
self.select(path)
.into_iter()
.min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
}
pub fn max_float(&self, path: KeyPaths<T, f64>) -> Option<f64> {
self.select(path)
.into_iter()
.max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
}
pub fn order_by<F>(&self, path: KeyPaths<T, F>) -> Vec<T>
where
F: Ord + Clone + 'static,
T: Clone,
{
let mut results = self.all();
results.sort_by_key(|item| path.get(item).cloned());
results
}
pub fn order_by_desc<F>(&self, path: KeyPaths<T, F>) -> Vec<T>
where
F: Ord + Clone + 'static,
T: Clone,
{
let mut results = self.all();
results.sort_by(|a, b| {
let a_val = path.get(a).cloned();
let b_val = path.get(b).cloned();
b_val.cmp(&a_val)
});
results
}
pub fn order_by_float(&self, path: KeyPaths<T, f64>) -> Vec<T>
where
T: Clone,
{
let mut results = self.all();
results.sort_by(|a, b| {
let a_val = path.get(a).cloned().unwrap_or(0.0);
let b_val = path.get(b).cloned().unwrap_or(0.0);
a_val.partial_cmp(&b_val).unwrap_or(std::cmp::Ordering::Equal)
});
results
}
pub fn order_by_float_desc(&self, path: KeyPaths<T, f64>) -> Vec<T>
where
T: Clone,
{
let mut results = self.all();
results.sort_by(|a, b| {
let a_val = path.get(a).cloned().unwrap_or(0.0);
let b_val = path.get(b).cloned().unwrap_or(0.0);
b_val.partial_cmp(&a_val).unwrap_or(std::cmp::Ordering::Equal)
});
results
}
pub fn group_by<F>(&self, path: KeyPaths<T, F>) -> HashMap<F, Vec<T>>
where
F: Eq + std::hash::Hash + Clone + 'static,
T: Clone,
{
let mut groups: HashMap<F, Vec<T>> = HashMap::new();
for lock in &self.locks {
if let Some(item) = lock.with_value(|item| {
if self.filters.iter().all(|f| f(item)) {
Some(item.clone())
} else {
None
}
})
.flatten()
{
if let Some(key) = path.get(&item).cloned() {
groups.entry(key).or_insert_with(Vec::new).push(item);
}
}
}
groups
}
pub fn min_timestamp(&self, path: KeyPaths<T, i64>) -> Option<i64> {
self.locks
.iter()
.filter_map(|lock| {
lock.with_value(|item| {
if self.filters.iter().all(|f| f(item)) {
path.get(item).cloned()
} else {
None
}
})
.flatten()
})
.min()
}
pub fn max_timestamp(&self, path: KeyPaths<T, i64>) -> Option<i64> {
self.locks
.iter()
.filter_map(|lock| {
lock.with_value(|item| {
if self.filters.iter().all(|f| f(item)) {
path.get(item).cloned()
} else {
None
}
})
.flatten()
})
.max()
}
pub fn avg_timestamp(&self, path: KeyPaths<T, i64>) -> Option<i64> {
let items: Vec<i64> = self.locks
.iter()
.filter_map(|lock| {
lock.with_value(|item| {
if self.filters.iter().all(|f| f(item)) {
path.get(item).cloned()
} else {
None
}
})
.flatten()
})
.collect();
if items.is_empty() {
None
} else {
Some(items.iter().sum::<i64>() / items.len() as i64)
}
}
pub fn sum_timestamp(&self, path: KeyPaths<T, i64>) -> i64 {
self.locks
.iter()
.filter_map(|lock| {
lock.with_value(|item| {
if self.filters.iter().all(|f| f(item)) {
path.get(item).cloned()
} else {
None
}
})
.flatten()
})
.sum()
}
pub fn count_timestamp(&self, path: KeyPaths<T, i64>) -> usize {
self.locks
.iter()
.filter(|lock| {
lock.with_value(|item| {
if self.filters.iter().all(|f| f(item)) {
path.get(item).is_some()
} else {
false
}
})
.unwrap_or(false)
})
.count()
}
pub fn where_after_timestamp(self, path: KeyPaths<T, i64>, reference: i64) -> Self {
self.where_(path, move |timestamp| timestamp > &reference)
}
pub fn where_before_timestamp(self, path: KeyPaths<T, i64>, reference: i64) -> Self {
self.where_(path, move |timestamp| timestamp < &reference)
}
pub fn where_between_timestamp(self, path: KeyPaths<T, i64>, start: i64, end: i64) -> Self {
self.where_(path, move |timestamp| timestamp >= &start && timestamp <= &end)
}
pub fn where_last_days_timestamp(self, path: KeyPaths<T, i64>, days: i64) -> Self {
let now = chrono::Utc::now().timestamp_millis();
let cutoff = now - (days * 24 * 60 * 60 * 1000); self.where_after_timestamp(path, cutoff)
}
pub fn where_next_days_timestamp(self, path: KeyPaths<T, i64>, days: i64) -> Self {
let now = chrono::Utc::now().timestamp_millis();
let cutoff = now + (days * 24 * 60 * 60 * 1000); self.where_before_timestamp(path, cutoff)
}
pub fn where_last_hours_timestamp(self, path: KeyPaths<T, i64>, hours: i64) -> Self {
let now = chrono::Utc::now().timestamp_millis();
let cutoff = now - (hours * 60 * 60 * 1000); self.where_after_timestamp(path, cutoff)
}
pub fn where_next_hours_timestamp(self, path: KeyPaths<T, i64>, hours: i64) -> Self {
let now = chrono::Utc::now().timestamp_millis();
let cutoff = now + (hours * 60 * 60 * 1000); self.where_before_timestamp(path, cutoff)
}
pub fn where_last_minutes_timestamp(self, path: KeyPaths<T, i64>, minutes: i64) -> Self {
let now = chrono::Utc::now().timestamp_millis();
let cutoff = now - (minutes * 60 * 1000); self.where_after_timestamp(path, cutoff)
}
pub fn where_next_minutes_timestamp(self, path: KeyPaths<T, i64>, minutes: i64) -> Self {
let now = chrono::Utc::now().timestamp_millis();
let cutoff = now + (minutes * 60 * 1000); self.where_before_timestamp(path, cutoff)
}
}
pub trait LockQueryable<T, L>
where
L: LockValue<T>,
{
fn lock_query(&self) -> LockQuery<'_, T, L>;
}
impl<K, V> LockQueryable<V, Arc<RwLock<V>>> for HashMap<K, Arc<RwLock<V>>>
where
K: Eq + std::hash::Hash,
{
fn lock_query(&self) -> LockQuery<'_, V, Arc<RwLock<V>>> {
LockQuery::from_locks(self.values().collect())
}
}
impl<K, V> LockQueryable<V, Arc<Mutex<V>>> for HashMap<K, Arc<Mutex<V>>>
where
K: Eq + std::hash::Hash,
{
fn lock_query(&self) -> LockQuery<'_, V, Arc<Mutex<V>>> {
LockQuery::from_locks(self.values().collect())
}
}
impl<T> LockQueryable<T, Arc<RwLock<T>>> for Vec<Arc<RwLock<T>>> {
fn lock_query(&self) -> LockQuery<'_, T, Arc<RwLock<T>>> {
LockQuery::from_locks(self.iter().collect())
}
}
impl<T> LockQueryable<T, Arc<Mutex<T>>> for Vec<Arc<Mutex<T>>> {
fn lock_query(&self) -> LockQuery<'_, T, Arc<Mutex<T>>> {
LockQuery::from_locks(self.iter().collect())
}
}
use crate::lock_lazy::LockLazyQuery;
pub trait LockLazyQueryable<T, L>
where
L: LockValue<T>,
{
fn lock_lazy_query(&self) -> LockLazyQuery<'_, T, L, impl Iterator<Item = &L>>;
}
impl<K, V> LockLazyQueryable<V, Arc<RwLock<V>>> for HashMap<K, Arc<RwLock<V>>>
where
K: Eq + std::hash::Hash,
{
fn lock_lazy_query(&self) -> LockLazyQuery<'_, V, Arc<RwLock<V>>, impl Iterator<Item = &Arc<RwLock<V>>>> {
LockLazyQuery::new(self.values())
}
}
impl<K, V> LockLazyQueryable<V, Arc<Mutex<V>>> for HashMap<K, Arc<Mutex<V>>>
where
K: Eq + std::hash::Hash,
{
fn lock_lazy_query(&self) -> LockLazyQuery<'_, V, Arc<Mutex<V>>, impl Iterator<Item = &Arc<Mutex<V>>>> {
LockLazyQuery::new(self.values())
}
}
impl<T> LockLazyQueryable<T, Arc<RwLock<T>>> for Vec<Arc<RwLock<T>>> {
fn lock_lazy_query(&self) -> LockLazyQuery<'_, T, Arc<RwLock<T>>, impl Iterator<Item = &Arc<RwLock<T>>>> {
LockLazyQuery::new(self.iter())
}
}
impl<T> LockLazyQueryable<T, Arc<Mutex<T>>> for Vec<Arc<Mutex<T>>> {
fn lock_lazy_query(&self) -> LockLazyQuery<'_, T, Arc<Mutex<T>>, impl Iterator<Item = &Arc<Mutex<T>>>> {
LockLazyQuery::new(self.iter())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::{Arc, RwLock};
use key_paths_derive::Keypath;
#[derive(Clone, Keypath)]
struct Product {
id: u32,
name: String,
price: f64,
category: String,
}
fn create_test_map() -> HashMap<String, Arc<RwLock<Product>>> {
let mut map = HashMap::new();
map.insert(
"p1".to_string(),
Arc::new(RwLock::new(Product {
id: 1,
name: "Laptop".to_string(),
price: 999.99,
category: "Electronics".to_string(),
})),
);
map.insert(
"p2".to_string(),
Arc::new(RwLock::new(Product {
id: 2,
name: "Chair".to_string(),
price: 299.99,
category: "Furniture".to_string(),
})),
);
map.insert(
"p3".to_string(),
Arc::new(RwLock::new(Product {
id: 3,
name: "Mouse".to_string(),
price: 29.99,
category: "Electronics".to_string(),
})),
);
map
}
#[test]
fn test_lock_query_where() {
let map = create_test_map();
let query = map.lock_query();
let count = query
.where_(Product::category(), |cat| cat == "Electronics")
.count();
assert_eq!(count, 2);
}
#[test]
fn test_lock_query_select() {
let map = create_test_map();
let names = map
.lock_query()
.select(Product::name());
assert_eq!(names.len(), 3);
}
#[test]
fn test_lock_query_sum() {
let map = create_test_map();
let total = map
.lock_query()
.sum(Product::price());
assert!((total - 1329.97).abs() < 0.01);
}
#[test]
fn test_lock_query_group_by() {
let map = create_test_map();
let groups = map
.lock_query()
.group_by(Product::category());
assert_eq!(groups.len(), 2);
assert_eq!(groups.get("Electronics").unwrap().len(), 2);
}
#[test]
fn test_lock_query_order_by() {
let map = create_test_map();
let sorted = map
.lock_query()
.order_by_float(Product::price());
assert_eq!(sorted[0].price, 29.99);
assert_eq!(sorted[2].price, 999.99);
}
}