#[cfg(feature = "parallel")]
use {
rayon::prelude::*,
key_paths_core::KeyPaths,
std::marker::PhantomData,
std::time::SystemTime,
};
#[cfg(feature = "datetime")]
#[allow(unused_imports)]
use chrono::DateTime;
#[cfg(feature = "parallel")]
pub struct LazyParallelQuery<'a, T: 'static + Send + Sync> {
data: &'a [T],
filter_groups: Vec<FilterGroup<'a, T>>,
_phantom: PhantomData<&'a T>,
}
#[cfg(feature = "parallel")]
enum FilterGroup<'a, T: 'static + Send + Sync> {
And(Vec<Box<dyn Fn(&T) -> bool + Send + Sync + 'a>>),
Or(Vec<Box<dyn Fn(&T) -> bool + Send + Sync + 'a>>),
}
#[cfg(feature = "parallel")]
impl<'a, T: 'static + Send + Sync> FilterGroup<'a, T> {
fn evaluate(&self, item: &T) -> bool {
match self {
FilterGroup::And(filters) => filters.iter().all(|f| f(item)),
FilterGroup::Or(filters) => filters.iter().any(|f| f(item)),
}
}
}
#[cfg(feature = "parallel")]
impl<'a, T: 'static + Send + Sync> LazyParallelQuery<'a, T> {
pub fn new(data: &'a [T]) -> Self {
Self {
data,
filter_groups: Vec::new(),
_phantom: PhantomData,
}
}
pub fn where_<F>(mut self, path: KeyPaths<T, F>, predicate: impl Fn(&F) -> bool + Send + Sync + 'static) -> Self
where
F: 'static + Send + Sync,
{
let filter = Box::new(move |item: &T| {
path.get(item).map_or(false, |val| predicate(val))
});
match self.filter_groups.last_mut() {
Some(FilterGroup::Or(filters)) => {
filters.push(filter);
}
Some(FilterGroup::And(filters)) => {
filters.push(filter);
}
None => {
self.filter_groups.push(FilterGroup::And(vec![filter]));
}
}
self
}
pub fn and<F>(mut self, path: KeyPaths<T, F>, predicate: impl Fn(&F) -> bool + Send + Sync + 'static) -> Self
where
F: 'static + Send + Sync,
{
let filter = Box::new(move |item: &T| {
path.get(item).map_or(false, |val| predicate(val))
});
match self.filter_groups.last_mut() {
Some(FilterGroup::And(filters)) => {
filters.push(filter);
}
_ => {
self.filter_groups.push(FilterGroup::And(vec![filter]));
}
}
self
}
pub fn or<F>(mut self, path: KeyPaths<T, F>, predicate: impl Fn(&F) -> bool + Send + Sync + 'static) -> Self
where
F: 'static + Send + Sync,
{
let filter = Box::new(move |item: &T| {
path.get(item).map_or(false, |val| predicate(val))
});
match self.filter_groups.last_mut() {
Some(FilterGroup::Or(filters)) => {
filters.push(filter);
}
_ => {
self.filter_groups.push(FilterGroup::Or(vec![filter]));
}
}
self
}
fn evaluate_filters(&self, item: &T) -> bool {
let (and_groups, or_groups): (Vec<_>, Vec<_>) = self.filter_groups
.iter()
.partition(|group| matches!(group, FilterGroup::And(_)));
match (and_groups.is_empty(), or_groups.is_empty()) {
(false, true) => and_groups.iter().all(|group| group.evaluate(item)),
(true, false) => or_groups.iter().any(|group| group.evaluate(item)),
(false, false) => {
let all_and_pass = and_groups.iter().all(|group| group.evaluate(item));
let any_or_pass = or_groups.iter().any(|group| group.evaluate(item));
all_and_pass || any_or_pass
}
(true, true) => true,
}
}
pub fn collect_parallel(&self) -> Vec<&'a T> {
self.data
.par_iter()
.filter(|item| self.evaluate_filters(item))
.collect()
}
pub fn first_parallel(&self) -> Option<&'a T> {
self.data
.par_iter()
.find_any(|item| self.evaluate_filters(item))
}
pub fn count_parallel(&self) -> usize {
self.data
.par_iter()
.filter(|item| self.evaluate_filters(item))
.count()
}
pub fn any_parallel(&self) -> bool {
self.data
.par_iter()
.any(|item| self.evaluate_filters(item))
}
pub fn all_match_parallel<P>(&self, predicate: P) -> bool
where
P: Fn(&'a T) -> bool + Send + Sync,
{
self.data
.par_iter()
.filter(|item| self.evaluate_filters(item))
.all(predicate)
}
pub fn for_each_parallel<F>(&self, f: F)
where
F: Fn(&'a T) + Send + Sync,
{
self.data
.par_iter()
.filter(|item| self.evaluate_filters(item))
.for_each(f)
}
pub fn fold_parallel<B, F>(&self, init: B, f: F) -> B
where
B: Send + Sync,
F: Fn(B, &'a T) -> B + Send + Sync,
{
let items: Vec<&'a T> = self.data
.par_iter()
.filter(|item| self.evaluate_filters(item))
.collect();
items.into_iter().fold(init, f)
}
pub fn find_parallel<P>(&self, predicate: P) -> Option<&'a T>
where
P: Fn(&&'a T) -> bool + Send + Sync,
{
self.data
.par_iter()
.filter(|item| self.evaluate_filters(item))
.find_any(predicate)
}
pub fn select_parallel<F>(&self, path: KeyPaths<T, F>) -> Vec<F>
where
F: Clone + Send + Sync + 'static,
{
self.data
.par_iter()
.filter(|item| self.evaluate_filters(item))
.filter_map(|item| path.get(item).cloned())
.collect()
}
pub fn map_items_parallel<F, O>(&self, f: F) -> Vec<O>
where
F: Fn(&'a T) -> O + Send + Sync,
O: Send,
{
self.data
.par_iter()
.filter(|item| self.evaluate_filters(item))
.map(f)
.collect()
}
pub fn take_parallel(&self, n: usize) -> Vec<&'a T> {
let mut results: Vec<&'a T> = self.data
.par_iter()
.filter(|item| self.evaluate_filters(item))
.collect();
results.truncate(n);
results
}
pub fn skip_parallel(&self, n: usize) -> Vec<&'a T> {
let results: Vec<&'a T> = self.data
.par_iter()
.filter(|item| self.evaluate_filters(item))
.collect();
results.into_iter().skip(n).collect()
}
}
#[cfg(feature = "parallel")]
impl<'a, T: 'static + Send + Sync> LazyParallelQuery<'a, T> {
pub fn sum_by_parallel<F>(&self, path: KeyPaths<T, F>) -> F
where
F: Clone + std::ops::Add<Output = F> + Default + Send + Sync + std::iter::Sum + 'static,
{
self.data
.par_iter()
.filter(|item| self.evaluate_filters(item))
.filter_map(|item| path.get(item).cloned())
.sum()
}
pub fn avg_by_parallel(&self, path: KeyPaths<T, f64>) -> Option<f64>
where
T: Send + Sync,
{
let items: Vec<f64> = self
.data
.par_iter()
.filter(|item| self.evaluate_filters(item))
.filter_map(|item| path.get(item).cloned())
.collect();
if items.is_empty() {
None
} else {
Some(items.par_iter().sum::<f64>() / items.len() as f64)
}
}
pub fn min_by_parallel<F>(&self, path: KeyPaths<T, F>) -> Option<F>
where
F: Ord + Clone + Send + Sync + 'static,
{
self.data
.par_iter()
.filter(|item| self.evaluate_filters(item))
.filter_map(|item| path.get(item).cloned())
.min()
}
pub fn max_by_parallel<F>(&self, path: KeyPaths<T, F>) -> Option<F>
where
F: Ord + Clone + Send + Sync + 'static,
{
self.data
.par_iter()
.filter(|item| self.evaluate_filters(item))
.filter_map(|item| path.get(item).cloned())
.max()
}
pub fn min_by_float_parallel(&self, path: KeyPaths<T, f64>) -> Option<f64>
where
T: Send + Sync,
{
self.data
.par_iter()
.filter(|item| self.evaluate_filters(item))
.filter_map(|item| path.get(item).cloned())
.min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
}
pub fn max_by_float_parallel(&self, path: KeyPaths<T, f64>) -> Option<f64>
where
T: Send + Sync,
{
self.data
.par_iter()
.filter(|item| self.evaluate_filters(item))
.filter_map(|item| path.get(item).cloned())
.max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
}
}
#[cfg(feature = "parallel")]
impl<'a, T: 'static + Send + Sync> LazyParallelQuery<'a, T> {
pub fn where_after_systemtime_parallel(self, path: KeyPaths<T, SystemTime>, reference: SystemTime) -> Self {
self.where_(path, move |time| time > &reference)
}
pub fn where_before_systemtime_parallel(self, path: KeyPaths<T, SystemTime>, reference: SystemTime) -> Self {
self.where_(path, move |time| time < &reference)
}
pub fn where_between_systemtime_parallel(
self,
path: KeyPaths<T, SystemTime>,
start: SystemTime,
end: SystemTime,
) -> Self {
self.where_(path, move |time| time >= &start && time <= &end)
}
}
#[cfg(all(feature = "parallel", feature = "datetime"))]
impl<'a, T: 'static + Send + Sync> LazyParallelQuery<'a, T> {
pub fn where_after_parallel<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, reference: DateTime<Tz>) -> Self
where
Tz: TimeZone + 'static,
Tz::Offset: std::fmt::Display + Send + Sync,
{
self.where_(path, move |time| time > &reference)
}
pub fn where_before_parallel<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, reference: DateTime<Tz>) -> Self
where
Tz: TimeZone + 'static,
Tz::Offset: std::fmt::Display + Send + Sync,
{
self.where_(path, move |time| time < &reference)
}
pub fn where_between_parallel<Tz>(
self,
path: KeyPaths<T, DateTime<Tz>>,
start: DateTime<Tz>,
end: DateTime<Tz>,
) -> Self
where
Tz: TimeZone + 'static,
Tz::Offset: std::fmt::Display + Send + Sync,
{
self.where_(path, move |time| time >= &start && time <= &end)
}
pub fn where_today_parallel<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, now: DateTime<Tz>) -> Self
where
Tz: TimeZone + 'static,
Tz::Offset: std::fmt::Display + Send + Sync,
{
self.where_(path, move |time| {
time.date_naive() == now.date_naive()
})
}
pub fn where_year_parallel<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, year: i32) -> Self
where
Tz: TimeZone + 'static,
Tz::Offset: std::fmt::Display + Send + Sync,
{
use chrono::Datelike;
self.where_(path, move |time| time.year() == year)
}
pub fn where_month_parallel<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, month: u32) -> Self
where
Tz: TimeZone + 'static,
Tz::Offset: std::fmt::Display + Send + Sync,
{
use chrono::Datelike;
self.where_(path, move |time| time.month() == month)
}
pub fn where_day_parallel<Tz>(self, path: KeyPaths<T, DateTime<Tz>>, day: u32) -> Self
where
Tz: TimeZone + 'static,
Tz::Offset: std::fmt::Display + Send + Sync,
{
use chrono::Datelike;
self.where_(path, move |time| time.day() == day)
}
pub fn where_weekend_parallel<Tz>(self, path: KeyPaths<T, DateTime<Tz>>) -> Self
where
Tz: TimeZone + 'static,
Tz::Offset: std::fmt::Display + Send + Sync,
{
use chrono::Datelike;
self.where_(path, |time| {
let weekday = time.weekday().num_days_from_monday();
weekday >= 5
})
}
pub fn where_weekday_parallel<Tz>(self, path: KeyPaths<T, DateTime<Tz>>) -> Self
where
Tz: TimeZone + 'static,
Tz::Offset: std::fmt::Display + Send + Sync,
{
use chrono::Datelike;
self.where_(path, |time| {
let weekday = time.weekday().num_days_from_monday();
weekday < 5
})
}
pub fn where_business_hours_parallel<Tz>(self, path: KeyPaths<T, DateTime<Tz>>) -> Self
where
Tz: TimeZone + 'static,
Tz::Offset: std::fmt::Display + Send + Sync,
{
use chrono::Timelike;
self.where_(path, |time| {
let hour = time.hour();
hour >= 9 && hour < 17
})
}
}
#[cfg(feature = "parallel")]
impl<'a, T: 'static + Send + Sync> LazyParallelQuery<'a, T> {
pub fn min_timestamp_parallel(&self, path: KeyPaths<T, i64>) -> Option<i64>
where
T: Send + Sync,
{
self.data
.par_iter()
.filter(|item| self.evaluate_filters(item))
.filter_map(|item| path.get(item).cloned())
.min()
}
pub fn max_timestamp_parallel(&self, path: KeyPaths<T, i64>) -> Option<i64>
where
T: Send + Sync,
{
self.data
.par_iter()
.filter(|item| self.evaluate_filters(item))
.filter_map(|item| path.get(item).cloned())
.max()
}
pub fn avg_timestamp_parallel(&self, path: KeyPaths<T, i64>) -> Option<i64>
where
T: Send + Sync,
{
let items: Vec<i64> = self
.data
.par_iter()
.filter(|item| self.evaluate_filters(item))
.filter_map(|item| path.get(item).cloned())
.collect();
if items.is_empty() {
None
} else {
Some(items.par_iter().sum::<i64>() / items.len() as i64)
}
}
pub fn sum_timestamp_parallel(&self, path: KeyPaths<T, i64>) -> i64
where
T: Send + Sync,
{
self.data
.par_iter()
.filter(|item| self.evaluate_filters(item))
.filter_map(|item| path.get(item).cloned())
.sum()
}
pub fn count_timestamp_parallel(&self, path: KeyPaths<T, i64>) -> usize
where
T: Send + Sync,
{
self.data
.par_iter()
.filter(|item| self.evaluate_filters(item))
.filter(|item| path.get(item).is_some())
.count()
}
pub fn where_after_timestamp_parallel(self, path: KeyPaths<T, i64>, reference: i64) -> Self {
self.where_(path, move |timestamp| timestamp > &reference)
}
pub fn where_before_timestamp_parallel(self, path: KeyPaths<T, i64>, reference: i64) -> Self {
self.where_(path, move |timestamp| timestamp < &reference)
}
pub fn where_between_timestamp_parallel(
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_parallel(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_parallel(path, cutoff)
}
pub fn where_next_days_timestamp_parallel(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_parallel(path, cutoff)
}
pub fn where_last_hours_timestamp_parallel(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_parallel(path, cutoff)
}
pub fn where_next_hours_timestamp_parallel(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_parallel(path, cutoff)
}
pub fn where_last_minutes_timestamp_parallel(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_parallel(path, cutoff)
}
pub fn where_next_minutes_timestamp_parallel(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_parallel(path, cutoff)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ext::LazyParallelQueryExt;
use key_paths_derive::Keypath;
#[derive(Debug, Clone, PartialEq, Keypath)]
struct Product {
id: u32,
name: String,
price: f64,
category: String,
stock: u32,
}
fn create_test_products() -> Vec<Product> {
vec![
Product {
id: 1,
name: "Laptop".to_string(),
price: 999.99,
category: "Electronics".to_string(),
stock: 5,
},
Product {
id: 2,
name: "Mouse".to_string(),
price: 29.99,
category: "Electronics".to_string(),
stock: 50,
},
Product {
id: 3,
name: "Keyboard".to_string(),
price: 79.99,
category: "Electronics".to_string(),
stock: 30,
},
Product {
id: 4,
name: "Desk Chair".to_string(),
price: 199.99,
category: "Furniture".to_string(),
stock: 8,
},
]
}
#[test]
fn test_parallel_and_operator() {
let products = create_test_products();
let results: Vec<_> = products
.lazy_parallel_query()
.where_(Product::price(), |&p| p < 100.0)
.and(Product::stock(), |&s| s > 10)
.collect_parallel();
assert_eq!(results.len(), 2);
assert!(results.iter().any(|p| p.name == "Mouse"));
assert!(results.iter().any(|p| p.name == "Keyboard"));
}
#[test]
fn test_parallel_or_operator() {
let products = create_test_products();
let results: Vec<_> = products
.lazy_parallel_query()
.where_(Product::price(), |&p| p < 50.0)
.or(Product::category(), |c| c == "Furniture")
.collect_parallel();
assert_eq!(results.len(), 2);
assert!(results.iter().any(|p| p.name == "Mouse"));
assert!(results.iter().any(|p| p.name == "Desk Chair"));
}
#[test]
fn test_parallel_complex_and_or() {
let products = create_test_products();
let results: Vec<_> = products
.lazy_parallel_query()
.where_(Product::price(), |&p| p < 100.0)
.and(Product::stock(), |&s| s > 10)
.or(Product::category(), |c| c == "Furniture")
.collect_parallel();
assert_eq!(results.len(), 3);
assert!(results.iter().any(|p| p.name == "Mouse"));
assert!(results.iter().any(|p| p.name == "Keyboard"));
assert!(results.iter().any(|p| p.name == "Desk Chair"));
}
}
#[cfg(feature = "parallel")]
pub trait LazyParallelQueryExt<T: 'static + Send + Sync> {
fn lazy_parallel_query(&self) -> LazyParallelQuery<T>;
}
#[cfg(feature = "parallel")]
impl<T: 'static + Send + Sync> LazyParallelQueryExt<T> for [T] {
fn lazy_parallel_query(&self) -> LazyParallelQuery<T> {
LazyParallelQuery::new(self)
}
}
#[cfg(feature = "parallel")]
impl<T: 'static + Send + Sync> LazyParallelQueryExt<T> for Vec<T> {
fn lazy_parallel_query(&self) -> LazyParallelQuery<T> {
LazyParallelQuery::new(self)
}
}