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