1use bumpalo::Bump;
59use std::borrow::Cow;
60
61use crate::filter::{Filter, FilterValue};
62
63pub struct FilterPool {
78 arena: Bump,
79}
80
81impl FilterPool {
82 pub fn new() -> Self {
86 Self { arena: Bump::new() }
87 }
88
89 pub fn with_capacity(capacity: usize) -> Self {
93 Self {
94 arena: Bump::with_capacity(capacity),
95 }
96 }
97
98 pub fn reset(&mut self) {
103 self.arena.reset();
104 }
105
106 pub fn allocated_bytes(&self) -> usize {
108 self.arena.allocated_bytes()
109 }
110
111 pub fn build<F>(&self, f: F) -> Filter
132 where
133 F: for<'a> FnOnce(&'a FilterBuilder<'a>) -> PooledFilter<'a>,
134 {
135 let builder = FilterBuilder::new(&self.arena);
136 let pooled = f(&builder);
137 pooled.materialize()
138 }
139}
140
141impl Default for FilterPool {
142 fn default() -> Self {
143 Self::new()
144 }
145}
146
147#[derive(Debug, Clone, Copy)]
152pub enum PooledFilter<'a> {
153 None,
155 Equals(&'a str, PooledValue<'a>),
157 NotEquals(&'a str, PooledValue<'a>),
159 Lt(&'a str, PooledValue<'a>),
161 Lte(&'a str, PooledValue<'a>),
163 Gt(&'a str, PooledValue<'a>),
165 Gte(&'a str, PooledValue<'a>),
167 In(&'a str, &'a [PooledValue<'a>]),
169 NotIn(&'a str, &'a [PooledValue<'a>]),
171 Contains(&'a str, PooledValue<'a>),
173 StartsWith(&'a str, PooledValue<'a>),
175 EndsWith(&'a str, PooledValue<'a>),
177 IsNull(&'a str),
179 IsNotNull(&'a str),
181 And(&'a [PooledFilter<'a>]),
183 Or(&'a [PooledFilter<'a>]),
185 Not(&'a PooledFilter<'a>),
187}
188
189impl<'a> PooledFilter<'a> {
190 pub fn materialize(&self) -> Filter {
194 match self {
195 PooledFilter::None => Filter::None,
196 PooledFilter::Equals(field, value) => {
197 Filter::Equals(Cow::Owned((*field).to_string()), value.materialize())
198 }
199 PooledFilter::NotEquals(field, value) => {
200 Filter::NotEquals(Cow::Owned((*field).to_string()), value.materialize())
201 }
202 PooledFilter::Lt(field, value) => {
203 Filter::Lt(Cow::Owned((*field).to_string()), value.materialize())
204 }
205 PooledFilter::Lte(field, value) => {
206 Filter::Lte(Cow::Owned((*field).to_string()), value.materialize())
207 }
208 PooledFilter::Gt(field, value) => {
209 Filter::Gt(Cow::Owned((*field).to_string()), value.materialize())
210 }
211 PooledFilter::Gte(field, value) => {
212 Filter::Gte(Cow::Owned((*field).to_string()), value.materialize())
213 }
214 PooledFilter::In(field, values) => Filter::In(
215 Cow::Owned((*field).to_string()),
216 values.iter().map(|v| v.materialize()).collect(),
217 ),
218 PooledFilter::NotIn(field, values) => Filter::NotIn(
219 Cow::Owned((*field).to_string()),
220 values.iter().map(|v| v.materialize()).collect(),
221 ),
222 PooledFilter::Contains(field, value) => {
223 Filter::Contains(Cow::Owned((*field).to_string()), value.materialize())
224 }
225 PooledFilter::StartsWith(field, value) => {
226 Filter::StartsWith(Cow::Owned((*field).to_string()), value.materialize())
227 }
228 PooledFilter::EndsWith(field, value) => {
229 Filter::EndsWith(Cow::Owned((*field).to_string()), value.materialize())
230 }
231 PooledFilter::IsNull(field) => Filter::IsNull(Cow::Owned((*field).to_string())),
232 PooledFilter::IsNotNull(field) => Filter::IsNotNull(Cow::Owned((*field).to_string())),
233 PooledFilter::And(filters) => Filter::And(
234 filters
235 .iter()
236 .map(|f| f.materialize())
237 .collect::<Vec<_>>()
238 .into_boxed_slice(),
239 ),
240 PooledFilter::Or(filters) => Filter::Or(
241 filters
242 .iter()
243 .map(|f| f.materialize())
244 .collect::<Vec<_>>()
245 .into_boxed_slice(),
246 ),
247 PooledFilter::Not(filter) => Filter::Not(Box::new(filter.materialize())),
248 }
249 }
250}
251
252#[derive(Debug, Clone, Copy)]
254pub enum PooledValue<'a> {
255 Null,
257 Bool(bool),
259 Int(i64),
261 Float(f64),
263 String(&'a str),
265 Json(&'a str),
267}
268
269impl<'a> PooledValue<'a> {
270 pub fn materialize(&self) -> FilterValue {
272 match self {
273 PooledValue::Null => FilterValue::Null,
274 PooledValue::Bool(b) => FilterValue::Bool(*b),
275 PooledValue::Int(i) => FilterValue::Int(*i),
276 PooledValue::Float(f) => FilterValue::Float(*f),
277 PooledValue::String(s) => FilterValue::String((*s).to_string()),
278 PooledValue::Json(s) => FilterValue::Json(serde_json::from_str(s).unwrap_or_default()),
279 }
280 }
281}
282
283pub struct FilterBuilder<'a> {
287 arena: &'a Bump,
288}
289
290impl<'a> FilterBuilder<'a> {
291 fn new(arena: &'a Bump) -> Self {
292 Self { arena }
293 }
294
295 fn alloc_str(&self, s: &str) -> &'a str {
297 self.arena.alloc_str(s)
298 }
299
300 fn alloc_filters(&self, filters: Vec<PooledFilter<'a>>) -> &'a [PooledFilter<'a>] {
302 self.arena.alloc_slice_fill_iter(filters)
303 }
304
305 fn alloc_values(&self, values: Vec<PooledValue<'a>>) -> &'a [PooledValue<'a>] {
307 self.arena.alloc_slice_fill_iter(values)
308 }
309
310 pub fn value<V: IntoPooledValue<'a>>(&self, v: V) -> PooledValue<'a> {
312 v.into_pooled(self)
313 }
314
315 pub fn none(&self) -> PooledFilter<'a> {
317 PooledFilter::None
318 }
319
320 pub fn eq<V: IntoPooledValue<'a>>(&self, field: &str, value: V) -> PooledFilter<'a> {
331 PooledFilter::Equals(self.alloc_str(field), value.into_pooled(self))
332 }
333
334 pub fn ne<V: IntoPooledValue<'a>>(&self, field: &str, value: V) -> PooledFilter<'a> {
336 PooledFilter::NotEquals(self.alloc_str(field), value.into_pooled(self))
337 }
338
339 pub fn lt<V: IntoPooledValue<'a>>(&self, field: &str, value: V) -> PooledFilter<'a> {
341 PooledFilter::Lt(self.alloc_str(field), value.into_pooled(self))
342 }
343
344 pub fn lte<V: IntoPooledValue<'a>>(&self, field: &str, value: V) -> PooledFilter<'a> {
346 PooledFilter::Lte(self.alloc_str(field), value.into_pooled(self))
347 }
348
349 pub fn gt<V: IntoPooledValue<'a>>(&self, field: &str, value: V) -> PooledFilter<'a> {
351 PooledFilter::Gt(self.alloc_str(field), value.into_pooled(self))
352 }
353
354 pub fn gte<V: IntoPooledValue<'a>>(&self, field: &str, value: V) -> PooledFilter<'a> {
356 PooledFilter::Gte(self.alloc_str(field), value.into_pooled(self))
357 }
358
359 pub fn is_in(&self, field: &str, values: Vec<PooledValue<'a>>) -> PooledFilter<'a> {
372 PooledFilter::In(self.alloc_str(field), self.alloc_values(values))
373 }
374
375 pub fn not_in(&self, field: &str, values: Vec<PooledValue<'a>>) -> PooledFilter<'a> {
377 PooledFilter::NotIn(self.alloc_str(field), self.alloc_values(values))
378 }
379
380 pub fn contains<V: IntoPooledValue<'a>>(&self, field: &str, value: V) -> PooledFilter<'a> {
382 PooledFilter::Contains(self.alloc_str(field), value.into_pooled(self))
383 }
384
385 pub fn starts_with<V: IntoPooledValue<'a>>(&self, field: &str, value: V) -> PooledFilter<'a> {
387 PooledFilter::StartsWith(self.alloc_str(field), value.into_pooled(self))
388 }
389
390 pub fn ends_with<V: IntoPooledValue<'a>>(&self, field: &str, value: V) -> PooledFilter<'a> {
392 PooledFilter::EndsWith(self.alloc_str(field), value.into_pooled(self))
393 }
394
395 pub fn is_null(&self, field: &str) -> PooledFilter<'a> {
397 PooledFilter::IsNull(self.alloc_str(field))
398 }
399
400 pub fn is_not_null(&self, field: &str) -> PooledFilter<'a> {
402 PooledFilter::IsNotNull(self.alloc_str(field))
403 }
404
405 pub fn and(&self, filters: Vec<PooledFilter<'a>>) -> PooledFilter<'a> {
422 let filters: Vec<_> = filters
424 .into_iter()
425 .filter(|f| !matches!(f, PooledFilter::None))
426 .collect();
427
428 match filters.len() {
429 0 => PooledFilter::None,
430 1 => filters.into_iter().next().unwrap(),
431 _ => PooledFilter::And(self.alloc_filters(filters)),
432 }
433 }
434
435 pub fn or(&self, filters: Vec<PooledFilter<'a>>) -> PooledFilter<'a> {
451 let filters: Vec<_> = filters
453 .into_iter()
454 .filter(|f| !matches!(f, PooledFilter::None))
455 .collect();
456
457 match filters.len() {
458 0 => PooledFilter::None,
459 1 => filters.into_iter().next().unwrap(),
460 _ => PooledFilter::Or(self.alloc_filters(filters)),
461 }
462 }
463
464 pub fn not(&self, filter: PooledFilter<'a>) -> PooledFilter<'a> {
475 if matches!(filter, PooledFilter::None) {
476 return PooledFilter::None;
477 }
478 PooledFilter::Not(self.arena.alloc(filter))
479 }
480}
481
482pub trait IntoPooledValue<'a> {
484 fn into_pooled(self, builder: &FilterBuilder<'a>) -> PooledValue<'a>;
485}
486
487impl<'a> IntoPooledValue<'a> for bool {
488 fn into_pooled(self, _builder: &FilterBuilder<'a>) -> PooledValue<'a> {
489 PooledValue::Bool(self)
490 }
491}
492
493impl<'a> IntoPooledValue<'a> for i32 {
494 fn into_pooled(self, _builder: &FilterBuilder<'a>) -> PooledValue<'a> {
495 PooledValue::Int(self as i64)
496 }
497}
498
499impl<'a> IntoPooledValue<'a> for i64 {
500 fn into_pooled(self, _builder: &FilterBuilder<'a>) -> PooledValue<'a> {
501 PooledValue::Int(self)
502 }
503}
504
505impl<'a> IntoPooledValue<'a> for f64 {
506 fn into_pooled(self, _builder: &FilterBuilder<'a>) -> PooledValue<'a> {
507 PooledValue::Float(self)
508 }
509}
510
511impl<'a> IntoPooledValue<'a> for &str {
512 fn into_pooled(self, builder: &FilterBuilder<'a>) -> PooledValue<'a> {
513 PooledValue::String(builder.alloc_str(self))
514 }
515}
516
517impl<'a> IntoPooledValue<'a> for String {
518 fn into_pooled(self, builder: &FilterBuilder<'a>) -> PooledValue<'a> {
519 PooledValue::String(builder.alloc_str(&self))
520 }
521}
522
523impl<'a> IntoPooledValue<'a> for PooledValue<'a> {
524 fn into_pooled(self, _builder: &FilterBuilder<'a>) -> PooledValue<'a> {
525 self
526 }
527}
528
529#[cfg(test)]
530mod tests {
531 use super::*;
532
533 #[test]
534 fn test_pool_basic_filter() {
535 let pool = FilterPool::new();
536 let filter = pool.build(|b| b.eq("id", 42));
537
538 assert!(matches!(filter, Filter::Equals(_, _)));
539 }
540
541 #[test]
542 fn test_pool_and_filter() {
543 let pool = FilterPool::new();
544 let filter = pool.build(|b| b.and(vec![b.eq("active", true), b.gt("score", 100)]));
545
546 assert!(matches!(filter, Filter::And(_)));
547 }
548
549 #[test]
550 fn test_pool_or_filter() {
551 let pool = FilterPool::new();
552 let filter = pool.build(|b| {
553 b.or(vec![
554 b.eq("status", "pending"),
555 b.eq("status", "processing"),
556 ])
557 });
558
559 assert!(matches!(filter, Filter::Or(_)));
560 }
561
562 #[test]
563 fn test_pool_nested_filter() {
564 let pool = FilterPool::new();
565 let filter = pool.build(|b| {
566 b.and(vec![
567 b.eq("active", true),
568 b.or(vec![b.gt("age", 18), b.eq("verified", true)]),
569 b.not(b.eq("deleted", true)),
570 ])
571 });
572
573 assert!(matches!(filter, Filter::And(_)));
574 }
575
576 #[test]
577 fn test_pool_in_filter() {
578 let pool = FilterPool::new();
579 let filter = pool.build(|b| {
580 b.is_in(
581 "status",
582 vec![
583 b.value("pending"),
584 b.value("processing"),
585 b.value("completed"),
586 ],
587 )
588 });
589
590 assert!(matches!(filter, Filter::In(_, _)));
591 }
592
593 #[test]
594 fn test_pool_reset() {
595 let mut pool = FilterPool::new();
596
597 let _ = pool.build(|b| b.eq("id", 1));
599 let bytes1 = pool.allocated_bytes();
600
601 pool.reset();
603
604 let _ = pool.build(|b| b.eq("id", 2));
606 let bytes2 = pool.allocated_bytes();
607
608 assert!(bytes2 <= bytes1 * 2); }
611
612 #[test]
613 fn test_pool_empty_and() {
614 let pool = FilterPool::new();
615 let filter = pool.build(|b| b.and(vec![]));
616
617 assert!(matches!(filter, Filter::None));
618 }
619
620 #[test]
621 fn test_pool_single_and() {
622 let pool = FilterPool::new();
623 let filter = pool.build(|b| b.and(vec![b.eq("id", 1)]));
624
625 assert!(matches!(filter, Filter::Equals(_, _)));
627 }
628
629 #[test]
630 fn test_pool_null_filters() {
631 let pool = FilterPool::new();
632 let filter = pool.build(|b| b.is_null("deleted_at"));
633
634 assert!(matches!(filter, Filter::IsNull(_)));
635 }
636
637 #[test]
638 fn test_pool_deeply_nested() {
639 let pool = FilterPool::new();
640
641 let filter = pool.build(|b| {
643 b.and(vec![
644 b.or(vec![
645 b.and(vec![b.eq("a", 1), b.eq("b", 2)]),
646 b.and(vec![b.eq("c", 3), b.eq("d", 4)]),
647 ]),
648 b.not(b.or(vec![b.eq("e", 5), b.eq("f", 6)])),
649 ])
650 });
651
652 assert!(matches!(filter, Filter::And(_)));
654
655 let (sql, params) = filter.to_sql(0);
657 assert!(sql.contains("AND"));
658 assert!(sql.contains("OR"));
659 assert!(sql.contains("NOT"));
660 assert_eq!(params.len(), 6);
661 }
662
663 #[test]
664 fn test_pool_string_values() {
665 let pool = FilterPool::new();
666 let filter = pool.build(|b| {
667 b.and(vec![
668 b.eq("name", "Alice"),
669 b.contains("email", "@example.com"),
670 b.starts_with("phone", "+1"),
671 ])
672 });
673
674 let (sql, params) = filter.to_sql(0);
675 assert!(sql.contains("LIKE"));
676 assert_eq!(params.len(), 3);
677 }
678}