use bumpalo::Bump;
use std::borrow::Cow;
use crate::filter::{Filter, FilterValue};
pub struct FilterPool {
arena: Bump,
}
impl FilterPool {
pub fn new() -> Self {
Self { arena: Bump::new() }
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
arena: Bump::with_capacity(capacity),
}
}
pub fn reset(&mut self) {
self.arena.reset();
}
pub fn allocated_bytes(&self) -> usize {
self.arena.allocated_bytes()
}
pub fn build<F>(&self, f: F) -> Filter
where
F: for<'a> FnOnce(&'a FilterBuilder<'a>) -> PooledFilter<'a>,
{
let builder = FilterBuilder::new(&self.arena);
let pooled = f(&builder);
pooled.materialize()
}
}
impl Default for FilterPool {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy)]
pub enum PooledFilter<'a> {
None,
Equals(&'a str, PooledValue<'a>),
NotEquals(&'a str, PooledValue<'a>),
Lt(&'a str, PooledValue<'a>),
Lte(&'a str, PooledValue<'a>),
Gt(&'a str, PooledValue<'a>),
Gte(&'a str, PooledValue<'a>),
In(&'a str, &'a [PooledValue<'a>]),
NotIn(&'a str, &'a [PooledValue<'a>]),
Contains(&'a str, PooledValue<'a>),
StartsWith(&'a str, PooledValue<'a>),
EndsWith(&'a str, PooledValue<'a>),
IsNull(&'a str),
IsNotNull(&'a str),
And(&'a [PooledFilter<'a>]),
Or(&'a [PooledFilter<'a>]),
Not(&'a PooledFilter<'a>),
}
impl<'a> PooledFilter<'a> {
pub fn materialize(&self) -> Filter {
match self {
PooledFilter::None => Filter::None,
PooledFilter::Equals(field, value) => {
Filter::Equals(Cow::Owned((*field).to_string()), value.materialize())
}
PooledFilter::NotEquals(field, value) => {
Filter::NotEquals(Cow::Owned((*field).to_string()), value.materialize())
}
PooledFilter::Lt(field, value) => {
Filter::Lt(Cow::Owned((*field).to_string()), value.materialize())
}
PooledFilter::Lte(field, value) => {
Filter::Lte(Cow::Owned((*field).to_string()), value.materialize())
}
PooledFilter::Gt(field, value) => {
Filter::Gt(Cow::Owned((*field).to_string()), value.materialize())
}
PooledFilter::Gte(field, value) => {
Filter::Gte(Cow::Owned((*field).to_string()), value.materialize())
}
PooledFilter::In(field, values) => Filter::In(
Cow::Owned((*field).to_string()),
values.iter().map(|v| v.materialize()).collect(),
),
PooledFilter::NotIn(field, values) => Filter::NotIn(
Cow::Owned((*field).to_string()),
values.iter().map(|v| v.materialize()).collect(),
),
PooledFilter::Contains(field, value) => {
Filter::Contains(Cow::Owned((*field).to_string()), value.materialize())
}
PooledFilter::StartsWith(field, value) => {
Filter::StartsWith(Cow::Owned((*field).to_string()), value.materialize())
}
PooledFilter::EndsWith(field, value) => {
Filter::EndsWith(Cow::Owned((*field).to_string()), value.materialize())
}
PooledFilter::IsNull(field) => Filter::IsNull(Cow::Owned((*field).to_string())),
PooledFilter::IsNotNull(field) => Filter::IsNotNull(Cow::Owned((*field).to_string())),
PooledFilter::And(filters) => Filter::And(
filters
.iter()
.map(|f| f.materialize())
.collect::<Vec<_>>()
.into_boxed_slice(),
),
PooledFilter::Or(filters) => Filter::Or(
filters
.iter()
.map(|f| f.materialize())
.collect::<Vec<_>>()
.into_boxed_slice(),
),
PooledFilter::Not(filter) => Filter::Not(Box::new(filter.materialize())),
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum PooledValue<'a> {
Null,
Bool(bool),
Int(i64),
Float(f64),
String(&'a str),
Json(&'a str),
}
impl<'a> PooledValue<'a> {
pub fn materialize(&self) -> FilterValue {
match self {
PooledValue::Null => FilterValue::Null,
PooledValue::Bool(b) => FilterValue::Bool(*b),
PooledValue::Int(i) => FilterValue::Int(*i),
PooledValue::Float(f) => FilterValue::Float(*f),
PooledValue::String(s) => FilterValue::String((*s).to_string()),
PooledValue::Json(s) => FilterValue::Json(serde_json::from_str(s).unwrap_or_default()),
}
}
}
pub struct FilterBuilder<'a> {
arena: &'a Bump,
}
impl<'a> FilterBuilder<'a> {
fn new(arena: &'a Bump) -> Self {
Self { arena }
}
fn alloc_str(&self, s: &str) -> &'a str {
self.arena.alloc_str(s)
}
fn alloc_filters(&self, filters: Vec<PooledFilter<'a>>) -> &'a [PooledFilter<'a>] {
self.arena.alloc_slice_fill_iter(filters)
}
fn alloc_values(&self, values: Vec<PooledValue<'a>>) -> &'a [PooledValue<'a>] {
self.arena.alloc_slice_fill_iter(values)
}
pub fn value<V: IntoPooledValue<'a>>(&self, v: V) -> PooledValue<'a> {
v.into_pooled(self)
}
pub fn none(&self) -> PooledFilter<'a> {
PooledFilter::None
}
pub fn eq<V: IntoPooledValue<'a>>(&self, field: &str, value: V) -> PooledFilter<'a> {
PooledFilter::Equals(self.alloc_str(field), value.into_pooled(self))
}
pub fn ne<V: IntoPooledValue<'a>>(&self, field: &str, value: V) -> PooledFilter<'a> {
PooledFilter::NotEquals(self.alloc_str(field), value.into_pooled(self))
}
pub fn lt<V: IntoPooledValue<'a>>(&self, field: &str, value: V) -> PooledFilter<'a> {
PooledFilter::Lt(self.alloc_str(field), value.into_pooled(self))
}
pub fn lte<V: IntoPooledValue<'a>>(&self, field: &str, value: V) -> PooledFilter<'a> {
PooledFilter::Lte(self.alloc_str(field), value.into_pooled(self))
}
pub fn gt<V: IntoPooledValue<'a>>(&self, field: &str, value: V) -> PooledFilter<'a> {
PooledFilter::Gt(self.alloc_str(field), value.into_pooled(self))
}
pub fn gte<V: IntoPooledValue<'a>>(&self, field: &str, value: V) -> PooledFilter<'a> {
PooledFilter::Gte(self.alloc_str(field), value.into_pooled(self))
}
pub fn is_in(&self, field: &str, values: Vec<PooledValue<'a>>) -> PooledFilter<'a> {
PooledFilter::In(self.alloc_str(field), self.alloc_values(values))
}
pub fn not_in(&self, field: &str, values: Vec<PooledValue<'a>>) -> PooledFilter<'a> {
PooledFilter::NotIn(self.alloc_str(field), self.alloc_values(values))
}
pub fn contains<V: IntoPooledValue<'a>>(&self, field: &str, value: V) -> PooledFilter<'a> {
PooledFilter::Contains(self.alloc_str(field), value.into_pooled(self))
}
pub fn starts_with<V: IntoPooledValue<'a>>(&self, field: &str, value: V) -> PooledFilter<'a> {
PooledFilter::StartsWith(self.alloc_str(field), value.into_pooled(self))
}
pub fn ends_with<V: IntoPooledValue<'a>>(&self, field: &str, value: V) -> PooledFilter<'a> {
PooledFilter::EndsWith(self.alloc_str(field), value.into_pooled(self))
}
pub fn is_null(&self, field: &str) -> PooledFilter<'a> {
PooledFilter::IsNull(self.alloc_str(field))
}
pub fn is_not_null(&self, field: &str) -> PooledFilter<'a> {
PooledFilter::IsNotNull(self.alloc_str(field))
}
pub fn and(&self, filters: Vec<PooledFilter<'a>>) -> PooledFilter<'a> {
let filters: Vec<_> = filters
.into_iter()
.filter(|f| !matches!(f, PooledFilter::None))
.collect();
match filters.len() {
0 => PooledFilter::None,
1 => filters.into_iter().next().unwrap(),
_ => PooledFilter::And(self.alloc_filters(filters)),
}
}
pub fn or(&self, filters: Vec<PooledFilter<'a>>) -> PooledFilter<'a> {
let filters: Vec<_> = filters
.into_iter()
.filter(|f| !matches!(f, PooledFilter::None))
.collect();
match filters.len() {
0 => PooledFilter::None,
1 => filters.into_iter().next().unwrap(),
_ => PooledFilter::Or(self.alloc_filters(filters)),
}
}
pub fn not(&self, filter: PooledFilter<'a>) -> PooledFilter<'a> {
if matches!(filter, PooledFilter::None) {
return PooledFilter::None;
}
PooledFilter::Not(self.arena.alloc(filter))
}
}
pub trait IntoPooledValue<'a> {
fn into_pooled(self, builder: &FilterBuilder<'a>) -> PooledValue<'a>;
}
impl<'a> IntoPooledValue<'a> for bool {
fn into_pooled(self, _builder: &FilterBuilder<'a>) -> PooledValue<'a> {
PooledValue::Bool(self)
}
}
impl<'a> IntoPooledValue<'a> for i32 {
fn into_pooled(self, _builder: &FilterBuilder<'a>) -> PooledValue<'a> {
PooledValue::Int(self as i64)
}
}
impl<'a> IntoPooledValue<'a> for i64 {
fn into_pooled(self, _builder: &FilterBuilder<'a>) -> PooledValue<'a> {
PooledValue::Int(self)
}
}
impl<'a> IntoPooledValue<'a> for f64 {
fn into_pooled(self, _builder: &FilterBuilder<'a>) -> PooledValue<'a> {
PooledValue::Float(self)
}
}
impl<'a> IntoPooledValue<'a> for &str {
fn into_pooled(self, builder: &FilterBuilder<'a>) -> PooledValue<'a> {
PooledValue::String(builder.alloc_str(self))
}
}
impl<'a> IntoPooledValue<'a> for String {
fn into_pooled(self, builder: &FilterBuilder<'a>) -> PooledValue<'a> {
PooledValue::String(builder.alloc_str(&self))
}
}
impl<'a> IntoPooledValue<'a> for PooledValue<'a> {
fn into_pooled(self, _builder: &FilterBuilder<'a>) -> PooledValue<'a> {
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pool_basic_filter() {
let pool = FilterPool::new();
let filter = pool.build(|b| b.eq("id", 42));
assert!(matches!(filter, Filter::Equals(_, _)));
}
#[test]
fn test_pool_and_filter() {
let pool = FilterPool::new();
let filter = pool.build(|b| b.and(vec![b.eq("active", true), b.gt("score", 100)]));
assert!(matches!(filter, Filter::And(_)));
}
#[test]
fn test_pool_or_filter() {
let pool = FilterPool::new();
let filter = pool.build(|b| {
b.or(vec![
b.eq("status", "pending"),
b.eq("status", "processing"),
])
});
assert!(matches!(filter, Filter::Or(_)));
}
#[test]
fn test_pool_nested_filter() {
let pool = FilterPool::new();
let filter = pool.build(|b| {
b.and(vec![
b.eq("active", true),
b.or(vec![b.gt("age", 18), b.eq("verified", true)]),
b.not(b.eq("deleted", true)),
])
});
assert!(matches!(filter, Filter::And(_)));
}
#[test]
fn test_pool_in_filter() {
let pool = FilterPool::new();
let filter = pool.build(|b| {
b.is_in(
"status",
vec![
b.value("pending"),
b.value("processing"),
b.value("completed"),
],
)
});
assert!(matches!(filter, Filter::In(_, _)));
}
#[test]
fn test_pool_reset() {
let mut pool = FilterPool::new();
let _ = pool.build(|b| b.eq("id", 1));
let bytes1 = pool.allocated_bytes();
pool.reset();
let _ = pool.build(|b| b.eq("id", 2));
let bytes2 = pool.allocated_bytes();
assert!(bytes2 <= bytes1 * 2); }
#[test]
fn test_pool_empty_and() {
let pool = FilterPool::new();
let filter = pool.build(|b| b.and(vec![]));
assert!(matches!(filter, Filter::None));
}
#[test]
fn test_pool_single_and() {
let pool = FilterPool::new();
let filter = pool.build(|b| b.and(vec![b.eq("id", 1)]));
assert!(matches!(filter, Filter::Equals(_, _)));
}
#[test]
fn test_pool_null_filters() {
let pool = FilterPool::new();
let filter = pool.build(|b| b.is_null("deleted_at"));
assert!(matches!(filter, Filter::IsNull(_)));
}
#[test]
fn test_pool_deeply_nested() {
let pool = FilterPool::new();
let filter = pool.build(|b| {
b.and(vec![
b.or(vec![
b.and(vec![b.eq("a", 1), b.eq("b", 2)]),
b.and(vec![b.eq("c", 3), b.eq("d", 4)]),
]),
b.not(b.or(vec![b.eq("e", 5), b.eq("f", 6)])),
])
});
assert!(matches!(filter, Filter::And(_)));
let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
assert!(sql.contains("AND"));
assert!(sql.contains("OR"));
assert!(sql.contains("NOT"));
assert_eq!(params.len(), 6);
}
#[test]
fn test_pool_string_values() {
let pool = FilterPool::new();
let filter = pool.build(|b| {
b.and(vec![
b.eq("name", "Alice"),
b.contains("email", "@example.com"),
b.starts_with("phone", "+1"),
])
});
let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
assert!(sql.contains("LIKE"));
assert_eq!(params.len(), 3);
}
}