prax_query/
filter.rs

1//! Filter types for building WHERE clauses.
2//!
3//! This module provides the building blocks for constructing type-safe query filters.
4//!
5//! # Performance
6//!
7//! Field names use `Cow<'static, str>` for optimal performance:
8//! - Static strings (`&'static str`) are borrowed with zero allocation
9//! - Dynamic strings are stored as owned `String`
10//! - Use `.into()` from static strings for best performance
11//!
12//! # Examples
13//!
14//! ## Basic Filters
15//!
16//! ```rust
17//! use prax_query::filter::{Filter, FilterValue};
18//!
19//! // Equality filter - zero allocation with static str
20//! let filter = Filter::Equals("id".into(), FilterValue::Int(42));
21//!
22//! // String contains
23//! let filter = Filter::Contains("email".into(), FilterValue::String("@example.com".into()));
24//!
25//! // Greater than
26//! let filter = Filter::Gt("age".into(), FilterValue::Int(18));
27//! ```
28//!
29//! ## Combining Filters
30//!
31//! ```rust
32//! use prax_query::filter::{Filter, FilterValue};
33//!
34//! // AND combination - use Filter::and() for convenience
35//! let filter = Filter::and([
36//!     Filter::Equals("active".into(), FilterValue::Bool(true)),
37//!     Filter::Gt("score".into(), FilterValue::Int(100)),
38//! ]);
39//!
40//! // OR combination - use Filter::or() for convenience
41//! let filter = Filter::or([
42//!     Filter::Equals("status".into(), FilterValue::String("pending".into())),
43//!     Filter::Equals("status".into(), FilterValue::String("processing".into())),
44//! ]);
45//!
46//! // NOT
47//! let filter = Filter::Not(Box::new(
48//!     Filter::Equals("deleted".into(), FilterValue::Bool(true))
49//! ));
50//! ```
51//!
52//! ## Null Checks
53//!
54//! ```rust
55//! use prax_query::filter::{Filter, FilterValue};
56//!
57//! // Is null
58//! let filter = Filter::IsNull("deleted_at".into());
59//!
60//! // Is not null
61//! let filter = Filter::IsNotNull("verified_at".into());
62//! ```
63
64use serde::{Deserialize, Serialize};
65use smallvec::SmallVec;
66use std::borrow::Cow;
67use tracing::debug;
68
69/// A list of filter values for IN/NOT IN clauses.
70///
71/// Uses `Vec<FilterValue>` for minimal Filter enum size (~64 bytes).
72/// This prioritizes cache efficiency over avoiding small allocations.
73///
74/// # Performance
75///
76/// While this does allocate for IN clauses, the benefits are:
77/// - Filter enum fits in a single cache line (64 bytes)
78/// - Better memory locality for filter iteration
79/// - Smaller stack usage for complex queries
80///
81/// For truly allocation-free IN filters, use `Filter::in_static()` with
82/// a static slice reference.
83pub type ValueList = Vec<FilterValue>;
84
85/// SmallVec-based value list for hot paths where small IN clauses are common.
86/// Use this explicitly when you know IN clauses are small (≤8 elements).
87pub type SmallValueList = SmallVec<[FilterValue; 8]>;
88
89/// Large value list type with 32 elements inline.
90/// Use this for known large IN clauses (e.g., batch operations).
91pub type LargeValueList = SmallVec<[FilterValue; 32]>;
92
93/// A field name that can be either a static string (zero allocation) or an owned string.
94///
95/// Uses `Cow<'static, str>` for optimal performance:
96/// - Static strings are borrowed without allocation
97/// - Dynamic strings are stored as owned `String`
98///
99/// # Examples
100///
101/// ```rust
102/// use prax_query::FieldName;
103///
104/// // Static strings - zero allocation (Cow::Borrowed)
105/// let name: FieldName = "id".into();
106/// let name: FieldName = "email".into();
107/// let name: FieldName = "user_id".into();
108/// let name: FieldName = "created_at".into();
109///
110/// // Dynamic strings work too (Cow::Owned)
111/// let name: FieldName = format!("field_{}", 1).into();
112/// ```
113pub type FieldName = Cow<'static, str>;
114
115/// A filter value that can be used in comparisons.
116///
117/// # Examples
118///
119/// ```rust
120/// use prax_query::FilterValue;
121///
122/// // From integers
123/// let val: FilterValue = 42.into();
124/// let val: FilterValue = 42i64.into();
125///
126/// // From strings
127/// let val: FilterValue = "hello".into();
128/// let val: FilterValue = String::from("world").into();
129///
130/// // From booleans
131/// let val: FilterValue = true.into();
132///
133/// // From floats
134/// let val: FilterValue = 3.14f64.into();
135///
136/// // Null value
137/// let val = FilterValue::Null;
138///
139/// // From vectors
140/// let val: FilterValue = vec![1, 2, 3].into();
141///
142/// // From Option (Some becomes value, None becomes Null)
143/// let val: FilterValue = Some(42).into();
144/// let val: FilterValue = Option::<i32>::None.into();
145/// assert!(val.is_null());
146/// ```
147#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
148#[serde(untagged)]
149pub enum FilterValue {
150    /// Null value.
151    Null,
152    /// Boolean value.
153    Bool(bool),
154    /// Integer value.
155    Int(i64),
156    /// Float value.
157    Float(f64),
158    /// String value.
159    String(String),
160    /// JSON value.
161    Json(serde_json::Value),
162    /// List of values.
163    List(Vec<FilterValue>),
164}
165
166impl FilterValue {
167    /// Check if this is a null value.
168    pub fn is_null(&self) -> bool {
169        matches!(self, Self::Null)
170    }
171
172    /// Convert to SQL parameter placeholder.
173    pub fn to_sql_placeholder(&self, param_index: usize) -> String {
174        format!("${}", param_index)
175    }
176}
177
178impl From<bool> for FilterValue {
179    fn from(v: bool) -> Self {
180        Self::Bool(v)
181    }
182}
183
184impl From<i32> for FilterValue {
185    fn from(v: i32) -> Self {
186        Self::Int(v as i64)
187    }
188}
189
190impl From<i64> for FilterValue {
191    fn from(v: i64) -> Self {
192        Self::Int(v)
193    }
194}
195
196impl From<f64> for FilterValue {
197    fn from(v: f64) -> Self {
198        Self::Float(v)
199    }
200}
201
202impl From<String> for FilterValue {
203    fn from(v: String) -> Self {
204        Self::String(v)
205    }
206}
207
208impl From<&str> for FilterValue {
209    fn from(v: &str) -> Self {
210        Self::String(v.to_string())
211    }
212}
213
214impl<T: Into<FilterValue>> From<Vec<T>> for FilterValue {
215    fn from(v: Vec<T>) -> Self {
216        Self::List(v.into_iter().map(Into::into).collect())
217    }
218}
219
220impl<T: Into<FilterValue>> From<Option<T>> for FilterValue {
221    fn from(v: Option<T>) -> Self {
222        match v {
223            Some(v) => v.into(),
224            None => Self::Null,
225        }
226    }
227}
228
229/// Scalar filter operations.
230#[derive(Debug, Clone, PartialEq)]
231pub enum ScalarFilter<T> {
232    /// Equals the value.
233    Equals(T),
234    /// Not equals the value.
235    Not(Box<T>),
236    /// In a list of values.
237    In(Vec<T>),
238    /// Not in a list of values.
239    NotIn(Vec<T>),
240    /// Less than.
241    Lt(T),
242    /// Less than or equal.
243    Lte(T),
244    /// Greater than.
245    Gt(T),
246    /// Greater than or equal.
247    Gte(T),
248    /// Contains (for strings).
249    Contains(T),
250    /// Starts with (for strings).
251    StartsWith(T),
252    /// Ends with (for strings).
253    EndsWith(T),
254    /// Is null.
255    IsNull,
256    /// Is not null.
257    IsNotNull,
258}
259
260impl<T: Into<FilterValue>> ScalarFilter<T> {
261    /// Convert to a Filter with the given column name.
262    ///
263    /// The column name can be a static string (zero allocation) or an owned string.
264    /// For IN/NOT IN filters, uses SmallVec to avoid heap allocation for ≤16 values.
265    pub fn into_filter(self, column: impl Into<FieldName>) -> Filter {
266        let column = column.into();
267        match self {
268            Self::Equals(v) => Filter::Equals(column, v.into()),
269            Self::Not(v) => Filter::NotEquals(column, (*v).into()),
270            Self::In(values) => Filter::In(column, values.into_iter().map(Into::into).collect()),
271            Self::NotIn(values) => {
272                Filter::NotIn(column, values.into_iter().map(Into::into).collect())
273            }
274            Self::Lt(v) => Filter::Lt(column, v.into()),
275            Self::Lte(v) => Filter::Lte(column, v.into()),
276            Self::Gt(v) => Filter::Gt(column, v.into()),
277            Self::Gte(v) => Filter::Gte(column, v.into()),
278            Self::Contains(v) => Filter::Contains(column, v.into()),
279            Self::StartsWith(v) => Filter::StartsWith(column, v.into()),
280            Self::EndsWith(v) => Filter::EndsWith(column, v.into()),
281            Self::IsNull => Filter::IsNull(column),
282            Self::IsNotNull => Filter::IsNotNull(column),
283        }
284    }
285}
286
287/// A complete filter that can be converted to SQL.
288///
289/// # Size Optimization
290///
291/// The Filter enum is designed to fit in a single cache line (~64 bytes):
292/// - Field names use `Cow<'static, str>` (24 bytes)
293/// - Filter values use `FilterValue` (40 bytes)
294/// - IN/NOT IN use `Vec<FilterValue>` (24 bytes) instead of SmallVec
295/// - AND/OR use `Box<[Filter]>` (16 bytes)
296///
297/// This enables efficient iteration and better CPU cache utilization.
298///
299/// # Zero-Allocation Patterns
300///
301/// For maximum performance, use static strings:
302/// ```rust
303/// use prax_query::filter::{Filter, FilterValue};
304/// // Zero allocation - static string borrowed
305/// let filter = Filter::Equals("id".into(), FilterValue::Int(42));
306/// ```
307#[derive(Debug, Clone, PartialEq)]
308#[repr(C)] // Ensure predictable memory layout
309pub enum Filter {
310    /// No filter (always true).
311    None,
312
313    /// Equals comparison.
314    Equals(FieldName, FilterValue),
315    /// Not equals comparison.
316    NotEquals(FieldName, FilterValue),
317
318    /// Less than comparison.
319    Lt(FieldName, FilterValue),
320    /// Less than or equal comparison.
321    Lte(FieldName, FilterValue),
322    /// Greater than comparison.
323    Gt(FieldName, FilterValue),
324    /// Greater than or equal comparison.
325    Gte(FieldName, FilterValue),
326
327    /// In a list of values.
328    In(FieldName, ValueList),
329    /// Not in a list of values.
330    NotIn(FieldName, ValueList),
331
332    /// Contains (LIKE %value%).
333    Contains(FieldName, FilterValue),
334    /// Starts with (LIKE value%).
335    StartsWith(FieldName, FilterValue),
336    /// Ends with (LIKE %value).
337    EndsWith(FieldName, FilterValue),
338
339    /// Is null check.
340    IsNull(FieldName),
341    /// Is not null check.
342    IsNotNull(FieldName),
343
344    /// Logical AND of multiple filters.
345    ///
346    /// Uses `Box<[Filter]>` instead of `Vec<Filter>` to save 8 bytes per filter
347    /// (no capacity field needed since filters are immutable after construction).
348    And(Box<[Filter]>),
349    /// Logical OR of multiple filters.
350    ///
351    /// Uses `Box<[Filter]>` instead of `Vec<Filter>` to save 8 bytes per filter
352    /// (no capacity field needed since filters are immutable after construction).
353    Or(Box<[Filter]>),
354    /// Logical NOT of a filter.
355    Not(Box<Filter>),
356}
357
358impl Filter {
359    /// Create an empty filter (matches everything).
360    #[inline(always)]
361    pub fn none() -> Self {
362        Self::None
363    }
364
365    /// Check if this filter is empty.
366    #[inline(always)]
367    pub fn is_none(&self) -> bool {
368        matches!(self, Self::None)
369    }
370
371    /// Create an AND filter from an iterator of filters.
372    ///
373    /// Automatically filters out `None` filters and simplifies single-element combinations.
374    ///
375    /// For known small counts, prefer `and2`, `and3`, `and5`, or `and_n` for better performance.
376    #[inline]
377    pub fn and(filters: impl IntoIterator<Item = Filter>) -> Self {
378        let filters: Vec<_> = filters.into_iter().filter(|f| !f.is_none()).collect();
379        let count = filters.len();
380        let result = match count {
381            0 => Self::None,
382            1 => filters.into_iter().next().unwrap(),
383            _ => Self::And(filters.into_boxed_slice()),
384        };
385        debug!(count, "Filter::and() created");
386        result
387    }
388
389    /// Create an AND filter from exactly two filters.
390    ///
391    /// More efficient than `and([a, b])` - avoids Vec allocation.
392    #[inline(always)]
393    pub fn and2(a: Filter, b: Filter) -> Self {
394        match (a.is_none(), b.is_none()) {
395            (true, true) => Self::None,
396            (true, false) => b,
397            (false, true) => a,
398            (false, false) => Self::And(Box::new([a, b])),
399        }
400    }
401
402    /// Create an OR filter from an iterator of filters.
403    ///
404    /// Automatically filters out `None` filters and simplifies single-element combinations.
405    ///
406    /// For known small counts, prefer `or2`, `or3`, or `or_n` for better performance.
407    #[inline]
408    pub fn or(filters: impl IntoIterator<Item = Filter>) -> Self {
409        let filters: Vec<_> = filters.into_iter().filter(|f| !f.is_none()).collect();
410        let count = filters.len();
411        let result = match count {
412            0 => Self::None,
413            1 => filters.into_iter().next().unwrap(),
414            _ => Self::Or(filters.into_boxed_slice()),
415        };
416        debug!(count, "Filter::or() created");
417        result
418    }
419
420    /// Create an OR filter from exactly two filters.
421    ///
422    /// More efficient than `or([a, b])` - avoids Vec allocation.
423    #[inline(always)]
424    pub fn or2(a: Filter, b: Filter) -> Self {
425        match (a.is_none(), b.is_none()) {
426            (true, true) => Self::None,
427            (true, false) => b,
428            (false, true) => a,
429            (false, false) => Self::Or(Box::new([a, b])),
430        }
431    }
432
433    // ========================================================================
434    // Const Generic Constructors (Zero Vec Allocation)
435    // ========================================================================
436
437    /// Create an AND filter from a fixed-size array (const generic).
438    ///
439    /// This is the most efficient way to create AND filters when the count is
440    /// known at compile time. It avoids Vec allocation entirely.
441    ///
442    /// # Examples
443    ///
444    /// ```rust
445    /// use prax_query::filter::{Filter, FilterValue};
446    ///
447    /// let filter = Filter::and_n([
448    ///     Filter::Equals("a".into(), FilterValue::Int(1)),
449    ///     Filter::Equals("b".into(), FilterValue::Int(2)),
450    ///     Filter::Equals("c".into(), FilterValue::Int(3)),
451    /// ]);
452    /// ```
453    #[inline(always)]
454    pub fn and_n<const N: usize>(filters: [Filter; N]) -> Self {
455        // Convert array to boxed slice directly (no Vec intermediate)
456        Self::And(Box::new(filters))
457    }
458
459    /// Create an OR filter from a fixed-size array (const generic).
460    ///
461    /// This is the most efficient way to create OR filters when the count is
462    /// known at compile time. It avoids Vec allocation entirely.
463    #[inline(always)]
464    pub fn or_n<const N: usize>(filters: [Filter; N]) -> Self {
465        Self::Or(Box::new(filters))
466    }
467
468    /// Create an AND filter from exactly 3 filters.
469    #[inline(always)]
470    pub fn and3(a: Filter, b: Filter, c: Filter) -> Self {
471        Self::And(Box::new([a, b, c]))
472    }
473
474    /// Create an AND filter from exactly 4 filters.
475    #[inline(always)]
476    pub fn and4(a: Filter, b: Filter, c: Filter, d: Filter) -> Self {
477        Self::And(Box::new([a, b, c, d]))
478    }
479
480    /// Create an AND filter from exactly 5 filters.
481    #[inline(always)]
482    pub fn and5(a: Filter, b: Filter, c: Filter, d: Filter, e: Filter) -> Self {
483        Self::And(Box::new([a, b, c, d, e]))
484    }
485
486    /// Create an OR filter from exactly 3 filters.
487    #[inline(always)]
488    pub fn or3(a: Filter, b: Filter, c: Filter) -> Self {
489        Self::Or(Box::new([a, b, c]))
490    }
491
492    /// Create an OR filter from exactly 4 filters.
493    #[inline(always)]
494    pub fn or4(a: Filter, b: Filter, c: Filter, d: Filter) -> Self {
495        Self::Or(Box::new([a, b, c, d]))
496    }
497
498    /// Create an OR filter from exactly 5 filters.
499    #[inline(always)]
500    pub fn or5(a: Filter, b: Filter, c: Filter, d: Filter, e: Filter) -> Self {
501        Self::Or(Box::new([a, b, c, d, e]))
502    }
503
504    // ========================================================================
505    // Optimized IN Filter Constructors
506    // ========================================================================
507
508    /// Create an IN filter from an iterator of i64 values.
509    ///
510    /// This is optimized for integer lists, avoiding the generic `Into<FilterValue>`
511    /// conversion overhead.
512    #[inline]
513    pub fn in_i64(field: impl Into<FieldName>, values: impl IntoIterator<Item = i64>) -> Self {
514        let list: ValueList = values.into_iter().map(FilterValue::Int).collect();
515        Self::In(field.into(), list)
516    }
517
518    /// Create an IN filter from an iterator of i32 values.
519    #[inline]
520    pub fn in_i32(field: impl Into<FieldName>, values: impl IntoIterator<Item = i32>) -> Self {
521        let list: ValueList = values
522            .into_iter()
523            .map(|v| FilterValue::Int(v as i64))
524            .collect();
525        Self::In(field.into(), list)
526    }
527
528    /// Create an IN filter from an iterator of string values.
529    #[inline]
530    pub fn in_strings(
531        field: impl Into<FieldName>,
532        values: impl IntoIterator<Item = String>,
533    ) -> Self {
534        let list: ValueList = values.into_iter().map(FilterValue::String).collect();
535        Self::In(field.into(), list)
536    }
537
538    /// Create an IN filter from a pre-built ValueList.
539    ///
540    /// Use this when you've already constructed a ValueList to avoid re-collection.
541    #[inline]
542    pub fn in_values(field: impl Into<FieldName>, values: ValueList) -> Self {
543        Self::In(field.into(), values)
544    }
545
546    /// Create an IN filter from a range of i64 values.
547    ///
548    /// Highly optimized for sequential integer ranges.
549    #[inline]
550    pub fn in_range(field: impl Into<FieldName>, range: std::ops::Range<i64>) -> Self {
551        let list: ValueList = range.map(FilterValue::Int).collect();
552        Self::In(field.into(), list)
553    }
554
555    /// Create an IN filter from a pre-allocated i64 slice with exact capacity.
556    ///
557    /// This is the most efficient way to create IN filters for i64 values
558    /// when you have a slice available.
559    #[inline(always)]
560    pub fn in_i64_slice(field: impl Into<FieldName>, values: &[i64]) -> Self {
561        let mut list = Vec::with_capacity(values.len());
562        for &v in values {
563            list.push(FilterValue::Int(v));
564        }
565        Self::In(field.into(), list)
566    }
567
568    /// Create an IN filter for i32 values from a slice.
569    #[inline(always)]
570    pub fn in_i32_slice(field: impl Into<FieldName>, values: &[i32]) -> Self {
571        let mut list = Vec::with_capacity(values.len());
572        for &v in values {
573            list.push(FilterValue::Int(v as i64));
574        }
575        Self::In(field.into(), list)
576    }
577
578    /// Create an IN filter for string values from a slice.
579    #[inline(always)]
580    pub fn in_str_slice(field: impl Into<FieldName>, values: &[&str]) -> Self {
581        let mut list = Vec::with_capacity(values.len());
582        for &v in values {
583            list.push(FilterValue::String(v.to_string()));
584        }
585        Self::In(field.into(), list)
586    }
587
588    /// Create a NOT filter.
589    #[inline]
590    pub fn not(filter: Filter) -> Self {
591        if filter.is_none() {
592            return Self::None;
593        }
594        Self::Not(Box::new(filter))
595    }
596
597    /// Create an IN filter from a slice of values.
598    ///
599    /// This is more efficient than `Filter::In(field, values.into())` when you have a slice,
600    /// as it avoids intermediate collection.
601    ///
602    /// # Examples
603    ///
604    /// ```rust
605    /// use prax_query::filter::Filter;
606    ///
607    /// let ids: &[i64] = &[1, 2, 3, 4, 5];
608    /// let filter = Filter::in_slice("id", ids);
609    /// ```
610    #[inline]
611    pub fn in_slice<T: Into<FilterValue> + Clone>(
612        field: impl Into<FieldName>,
613        values: &[T],
614    ) -> Self {
615        let list: ValueList = values.iter().map(|v| v.clone().into()).collect();
616        Self::In(field.into(), list)
617    }
618
619    /// Create a NOT IN filter from a slice of values.
620    ///
621    /// # Examples
622    ///
623    /// ```rust
624    /// use prax_query::filter::Filter;
625    ///
626    /// let ids: &[i64] = &[1, 2, 3, 4, 5];
627    /// let filter = Filter::not_in_slice("id", ids);
628    /// ```
629    #[inline]
630    pub fn not_in_slice<T: Into<FilterValue> + Clone>(
631        field: impl Into<FieldName>,
632        values: &[T],
633    ) -> Self {
634        let list: ValueList = values.iter().map(|v| v.clone().into()).collect();
635        Self::NotIn(field.into(), list)
636    }
637
638    /// Create an IN filter from an array (const generic).
639    ///
640    /// This is useful when you know the size at compile time.
641    ///
642    /// # Examples
643    ///
644    /// ```rust
645    /// use prax_query::filter::Filter;
646    ///
647    /// let filter = Filter::in_array("status", ["active", "pending", "processing"]);
648    /// ```
649    #[inline]
650    pub fn in_array<T: Into<FilterValue>, const N: usize>(
651        field: impl Into<FieldName>,
652        values: [T; N],
653    ) -> Self {
654        let list: ValueList = values.into_iter().map(Into::into).collect();
655        Self::In(field.into(), list)
656    }
657
658    /// Create a NOT IN filter from an array (const generic).
659    #[inline]
660    pub fn not_in_array<T: Into<FilterValue>, const N: usize>(
661        field: impl Into<FieldName>,
662        values: [T; N],
663    ) -> Self {
664        let list: ValueList = values.into_iter().map(Into::into).collect();
665        Self::NotIn(field.into(), list)
666    }
667
668    /// Combine with another filter using AND.
669    pub fn and_then(self, other: Filter) -> Self {
670        if self.is_none() {
671            return other;
672        }
673        if other.is_none() {
674            return self;
675        }
676        match self {
677            Self::And(filters) => {
678                // Convert to Vec, add new filter, convert back to Box<[T]>
679                let mut vec: Vec<_> = filters.into_vec();
680                vec.push(other);
681                Self::And(vec.into_boxed_slice())
682            }
683            _ => Self::And(Box::new([self, other])),
684        }
685    }
686
687    /// Combine with another filter using OR.
688    pub fn or_else(self, other: Filter) -> Self {
689        if self.is_none() {
690            return other;
691        }
692        if other.is_none() {
693            return self;
694        }
695        match self {
696            Self::Or(filters) => {
697                // Convert to Vec, add new filter, convert back to Box<[T]>
698                let mut vec: Vec<_> = filters.into_vec();
699                vec.push(other);
700                Self::Or(vec.into_boxed_slice())
701            }
702            _ => Self::Or(Box::new([self, other])),
703        }
704    }
705
706    /// Generate SQL for this filter with parameter placeholders.
707    /// Returns (sql, params) where params are the values to bind.
708    pub fn to_sql(&self, param_offset: usize) -> (String, Vec<FilterValue>) {
709        let mut params = Vec::new();
710        let sql = self.to_sql_with_params(param_offset, &mut params);
711        (sql, params)
712    }
713
714    fn to_sql_with_params(&self, mut param_idx: usize, params: &mut Vec<FilterValue>) -> String {
715        match self {
716            Self::None => "TRUE".to_string(),
717
718            Self::Equals(col, val) => {
719                if val.is_null() {
720                    format!("{} IS NULL", col)
721                } else {
722                    params.push(val.clone());
723                    param_idx += params.len();
724                    format!("{} = ${}", col, param_idx)
725                }
726            }
727            Self::NotEquals(col, val) => {
728                if val.is_null() {
729                    format!("{} IS NOT NULL", col)
730                } else {
731                    params.push(val.clone());
732                    param_idx += params.len();
733                    format!("{} != ${}", col, param_idx)
734                }
735            }
736
737            Self::Lt(col, val) => {
738                params.push(val.clone());
739                param_idx += params.len();
740                format!("{} < ${}", col, param_idx)
741            }
742            Self::Lte(col, val) => {
743                params.push(val.clone());
744                param_idx += params.len();
745                format!("{} <= ${}", col, param_idx)
746            }
747            Self::Gt(col, val) => {
748                params.push(val.clone());
749                param_idx += params.len();
750                format!("{} > ${}", col, param_idx)
751            }
752            Self::Gte(col, val) => {
753                params.push(val.clone());
754                param_idx += params.len();
755                format!("{} >= ${}", col, param_idx)
756            }
757
758            Self::In(col, values) => {
759                if values.is_empty() {
760                    return "FALSE".to_string();
761                }
762                let placeholders: Vec<_> = values
763                    .iter()
764                    .map(|v| {
765                        params.push(v.clone());
766                        param_idx += params.len();
767                        format!("${}", param_idx)
768                    })
769                    .collect();
770                format!("{} IN ({})", col, placeholders.join(", "))
771            }
772            Self::NotIn(col, values) => {
773                if values.is_empty() {
774                    return "TRUE".to_string();
775                }
776                let placeholders: Vec<_> = values
777                    .iter()
778                    .map(|v| {
779                        params.push(v.clone());
780                        param_idx += params.len();
781                        format!("${}", param_idx)
782                    })
783                    .collect();
784                format!("{} NOT IN ({})", col, placeholders.join(", "))
785            }
786
787            Self::Contains(col, val) => {
788                if let FilterValue::String(s) = val {
789                    params.push(FilterValue::String(format!("%{}%", s)));
790                } else {
791                    params.push(val.clone());
792                }
793                param_idx += params.len();
794                format!("{} LIKE ${}", col, param_idx)
795            }
796            Self::StartsWith(col, val) => {
797                if let FilterValue::String(s) = val {
798                    params.push(FilterValue::String(format!("{}%", s)));
799                } else {
800                    params.push(val.clone());
801                }
802                param_idx += params.len();
803                format!("{} LIKE ${}", col, param_idx)
804            }
805            Self::EndsWith(col, val) => {
806                if let FilterValue::String(s) = val {
807                    params.push(FilterValue::String(format!("%{}", s)));
808                } else {
809                    params.push(val.clone());
810                }
811                param_idx += params.len();
812                format!("{} LIKE ${}", col, param_idx)
813            }
814
815            Self::IsNull(col) => format!("{} IS NULL", col),
816            Self::IsNotNull(col) => format!("{} IS NOT NULL", col),
817
818            Self::And(filters) => {
819                if filters.is_empty() {
820                    return "TRUE".to_string();
821                }
822                let parts: Vec<_> = filters
823                    .iter()
824                    .map(|f| f.to_sql_with_params(param_idx + params.len(), params))
825                    .collect();
826                format!("({})", parts.join(" AND "))
827            }
828            Self::Or(filters) => {
829                if filters.is_empty() {
830                    return "FALSE".to_string();
831                }
832                let parts: Vec<_> = filters
833                    .iter()
834                    .map(|f| f.to_sql_with_params(param_idx + params.len(), params))
835                    .collect();
836                format!("({})", parts.join(" OR "))
837            }
838            Self::Not(filter) => {
839                let inner = filter.to_sql_with_params(param_idx, params);
840                format!("NOT ({})", inner)
841            }
842        }
843    }
844
845    /// Create a builder for constructing AND filters with pre-allocated capacity.
846    ///
847    /// This is more efficient than using `Filter::and()` when you know the
848    /// approximate number of conditions upfront.
849    ///
850    /// # Examples
851    ///
852    /// ```rust
853    /// use prax_query::filter::{Filter, FilterValue};
854    ///
855    /// // Build an AND filter with pre-allocated capacity for 3 conditions
856    /// let filter = Filter::and_builder(3)
857    ///     .push(Filter::Equals("active".into(), FilterValue::Bool(true)))
858    ///     .push(Filter::Gt("score".into(), FilterValue::Int(100)))
859    ///     .push(Filter::IsNotNull("email".into()))
860    ///     .build();
861    /// ```
862    #[inline]
863    pub fn and_builder(capacity: usize) -> AndFilterBuilder {
864        AndFilterBuilder::with_capacity(capacity)
865    }
866
867    /// Create a builder for constructing OR filters with pre-allocated capacity.
868    ///
869    /// This is more efficient than using `Filter::or()` when you know the
870    /// approximate number of conditions upfront.
871    ///
872    /// # Examples
873    ///
874    /// ```rust
875    /// use prax_query::filter::{Filter, FilterValue};
876    ///
877    /// // Build an OR filter with pre-allocated capacity for 2 conditions
878    /// let filter = Filter::or_builder(2)
879    ///     .push(Filter::Equals("role".into(), FilterValue::String("admin".into())))
880    ///     .push(Filter::Equals("role".into(), FilterValue::String("moderator".into())))
881    ///     .build();
882    /// ```
883    #[inline]
884    pub fn or_builder(capacity: usize) -> OrFilterBuilder {
885        OrFilterBuilder::with_capacity(capacity)
886    }
887
888    /// Create a general-purpose filter builder.
889    ///
890    /// Use this for building complex filter trees with a fluent API.
891    ///
892    /// # Examples
893    ///
894    /// ```rust
895    /// use prax_query::filter::Filter;
896    ///
897    /// let filter = Filter::builder()
898    ///     .eq("status", "active")
899    ///     .gt("age", 18)
900    ///     .is_not_null("email")
901    ///     .build_and();
902    /// ```
903    #[inline]
904    pub fn builder() -> FluentFilterBuilder {
905        FluentFilterBuilder::new()
906    }
907}
908
909/// Builder for constructing AND filters with pre-allocated capacity.
910///
911/// This avoids vector reallocations when the number of conditions is known upfront.
912#[derive(Debug, Clone)]
913pub struct AndFilterBuilder {
914    filters: Vec<Filter>,
915}
916
917impl AndFilterBuilder {
918    /// Create a new builder with default capacity.
919    #[inline]
920    pub fn new() -> Self {
921        Self {
922            filters: Vec::new(),
923        }
924    }
925
926    /// Create a new builder with the specified capacity.
927    #[inline]
928    pub fn with_capacity(capacity: usize) -> Self {
929        Self {
930            filters: Vec::with_capacity(capacity),
931        }
932    }
933
934    /// Add a filter to the AND condition.
935    #[inline]
936    pub fn push(mut self, filter: Filter) -> Self {
937        if !filter.is_none() {
938            self.filters.push(filter);
939        }
940        self
941    }
942
943    /// Add multiple filters to the AND condition.
944    #[inline]
945    pub fn extend(mut self, filters: impl IntoIterator<Item = Filter>) -> Self {
946        self.filters
947            .extend(filters.into_iter().filter(|f| !f.is_none()));
948        self
949    }
950
951    /// Add a filter conditionally.
952    #[inline]
953    pub fn push_if(self, condition: bool, filter: Filter) -> Self {
954        if condition { self.push(filter) } else { self }
955    }
956
957    /// Add a filter conditionally, evaluating the closure only if condition is true.
958    #[inline]
959    pub fn push_if_some<F>(self, opt: Option<F>) -> Self
960    where
961        F: Into<Filter>,
962    {
963        match opt {
964            Some(f) => self.push(f.into()),
965            None => self,
966        }
967    }
968
969    /// Build the final AND filter.
970    #[inline]
971    pub fn build(self) -> Filter {
972        match self.filters.len() {
973            0 => Filter::None,
974            1 => self.filters.into_iter().next().unwrap(),
975            _ => Filter::And(self.filters.into_boxed_slice()),
976        }
977    }
978
979    /// Get the current number of filters.
980    #[inline]
981    pub fn len(&self) -> usize {
982        self.filters.len()
983    }
984
985    /// Check if the builder is empty.
986    #[inline]
987    pub fn is_empty(&self) -> bool {
988        self.filters.is_empty()
989    }
990}
991
992impl Default for AndFilterBuilder {
993    fn default() -> Self {
994        Self::new()
995    }
996}
997
998/// Builder for constructing OR filters with pre-allocated capacity.
999///
1000/// This avoids vector reallocations when the number of conditions is known upfront.
1001#[derive(Debug, Clone)]
1002pub struct OrFilterBuilder {
1003    filters: Vec<Filter>,
1004}
1005
1006impl OrFilterBuilder {
1007    /// Create a new builder with default capacity.
1008    #[inline]
1009    pub fn new() -> Self {
1010        Self {
1011            filters: Vec::new(),
1012        }
1013    }
1014
1015    /// Create a new builder with the specified capacity.
1016    #[inline]
1017    pub fn with_capacity(capacity: usize) -> Self {
1018        Self {
1019            filters: Vec::with_capacity(capacity),
1020        }
1021    }
1022
1023    /// Add a filter to the OR condition.
1024    #[inline]
1025    pub fn push(mut self, filter: Filter) -> Self {
1026        if !filter.is_none() {
1027            self.filters.push(filter);
1028        }
1029        self
1030    }
1031
1032    /// Add multiple filters to the OR condition.
1033    #[inline]
1034    pub fn extend(mut self, filters: impl IntoIterator<Item = Filter>) -> Self {
1035        self.filters
1036            .extend(filters.into_iter().filter(|f| !f.is_none()));
1037        self
1038    }
1039
1040    /// Add a filter conditionally.
1041    #[inline]
1042    pub fn push_if(self, condition: bool, filter: Filter) -> Self {
1043        if condition { self.push(filter) } else { self }
1044    }
1045
1046    /// Add a filter conditionally, evaluating the closure only if condition is true.
1047    #[inline]
1048    pub fn push_if_some<F>(self, opt: Option<F>) -> Self
1049    where
1050        F: Into<Filter>,
1051    {
1052        match opt {
1053            Some(f) => self.push(f.into()),
1054            None => self,
1055        }
1056    }
1057
1058    /// Build the final OR filter.
1059    #[inline]
1060    pub fn build(self) -> Filter {
1061        match self.filters.len() {
1062            0 => Filter::None,
1063            1 => self.filters.into_iter().next().unwrap(),
1064            _ => Filter::Or(self.filters.into_boxed_slice()),
1065        }
1066    }
1067
1068    /// Get the current number of filters.
1069    #[inline]
1070    pub fn len(&self) -> usize {
1071        self.filters.len()
1072    }
1073
1074    /// Check if the builder is empty.
1075    #[inline]
1076    pub fn is_empty(&self) -> bool {
1077        self.filters.is_empty()
1078    }
1079}
1080
1081impl Default for OrFilterBuilder {
1082    fn default() -> Self {
1083        Self::new()
1084    }
1085}
1086
1087/// A fluent builder for constructing filters with a convenient API.
1088///
1089/// This builder collects conditions and can produce either an AND or OR filter.
1090///
1091/// # Examples
1092///
1093/// ```rust
1094/// use prax_query::filter::Filter;
1095///
1096/// // Build an AND filter
1097/// let filter = Filter::builder()
1098///     .eq("active", true)
1099///     .gt("score", 100)
1100///     .contains("email", "@example.com")
1101///     .build_and();
1102///
1103/// // Build an OR filter with capacity hint
1104/// let filter = Filter::builder()
1105///     .with_capacity(3)
1106///     .eq("role", "admin")
1107///     .eq("role", "moderator")
1108///     .eq("role", "owner")
1109///     .build_or();
1110/// ```
1111#[derive(Debug, Clone)]
1112pub struct FluentFilterBuilder {
1113    filters: Vec<Filter>,
1114}
1115
1116impl FluentFilterBuilder {
1117    /// Create a new fluent builder.
1118    #[inline]
1119    pub fn new() -> Self {
1120        Self {
1121            filters: Vec::new(),
1122        }
1123    }
1124
1125    /// Set the capacity hint for the internal vector.
1126    #[inline]
1127    pub fn with_capacity(mut self, capacity: usize) -> Self {
1128        self.filters.reserve(capacity);
1129        self
1130    }
1131
1132    /// Add an equals filter.
1133    #[inline]
1134    pub fn eq<F, V>(mut self, field: F, value: V) -> Self
1135    where
1136        F: Into<FieldName>,
1137        V: Into<FilterValue>,
1138    {
1139        self.filters
1140            .push(Filter::Equals(field.into(), value.into()));
1141        self
1142    }
1143
1144    /// Add a not equals filter.
1145    #[inline]
1146    pub fn ne<F, V>(mut self, field: F, value: V) -> Self
1147    where
1148        F: Into<FieldName>,
1149        V: Into<FilterValue>,
1150    {
1151        self.filters
1152            .push(Filter::NotEquals(field.into(), value.into()));
1153        self
1154    }
1155
1156    /// Add a less than filter.
1157    #[inline]
1158    pub fn lt<F, V>(mut self, field: F, value: V) -> Self
1159    where
1160        F: Into<FieldName>,
1161        V: Into<FilterValue>,
1162    {
1163        self.filters.push(Filter::Lt(field.into(), value.into()));
1164        self
1165    }
1166
1167    /// Add a less than or equal filter.
1168    #[inline]
1169    pub fn lte<F, V>(mut self, field: F, value: V) -> Self
1170    where
1171        F: Into<FieldName>,
1172        V: Into<FilterValue>,
1173    {
1174        self.filters.push(Filter::Lte(field.into(), value.into()));
1175        self
1176    }
1177
1178    /// Add a greater than filter.
1179    #[inline]
1180    pub fn gt<F, V>(mut self, field: F, value: V) -> Self
1181    where
1182        F: Into<FieldName>,
1183        V: Into<FilterValue>,
1184    {
1185        self.filters.push(Filter::Gt(field.into(), value.into()));
1186        self
1187    }
1188
1189    /// Add a greater than or equal filter.
1190    #[inline]
1191    pub fn gte<F, V>(mut self, field: F, value: V) -> Self
1192    where
1193        F: Into<FieldName>,
1194        V: Into<FilterValue>,
1195    {
1196        self.filters.push(Filter::Gte(field.into(), value.into()));
1197        self
1198    }
1199
1200    /// Add an IN filter.
1201    #[inline]
1202    pub fn is_in<F, I, V>(mut self, field: F, values: I) -> Self
1203    where
1204        F: Into<FieldName>,
1205        I: IntoIterator<Item = V>,
1206        V: Into<FilterValue>,
1207    {
1208        self.filters.push(Filter::In(
1209            field.into(),
1210            values.into_iter().map(Into::into).collect(),
1211        ));
1212        self
1213    }
1214
1215    /// Add a NOT IN filter.
1216    #[inline]
1217    pub fn not_in<F, I, V>(mut self, field: F, values: I) -> Self
1218    where
1219        F: Into<FieldName>,
1220        I: IntoIterator<Item = V>,
1221        V: Into<FilterValue>,
1222    {
1223        self.filters.push(Filter::NotIn(
1224            field.into(),
1225            values.into_iter().map(Into::into).collect(),
1226        ));
1227        self
1228    }
1229
1230    /// Add a contains filter (LIKE %value%).
1231    #[inline]
1232    pub fn contains<F, V>(mut self, field: F, value: V) -> Self
1233    where
1234        F: Into<FieldName>,
1235        V: Into<FilterValue>,
1236    {
1237        self.filters
1238            .push(Filter::Contains(field.into(), value.into()));
1239        self
1240    }
1241
1242    /// Add a starts with filter (LIKE value%).
1243    #[inline]
1244    pub fn starts_with<F, V>(mut self, field: F, value: V) -> Self
1245    where
1246        F: Into<FieldName>,
1247        V: Into<FilterValue>,
1248    {
1249        self.filters
1250            .push(Filter::StartsWith(field.into(), value.into()));
1251        self
1252    }
1253
1254    /// Add an ends with filter (LIKE %value).
1255    #[inline]
1256    pub fn ends_with<F, V>(mut self, field: F, value: V) -> Self
1257    where
1258        F: Into<FieldName>,
1259        V: Into<FilterValue>,
1260    {
1261        self.filters
1262            .push(Filter::EndsWith(field.into(), value.into()));
1263        self
1264    }
1265
1266    /// Add an IS NULL filter.
1267    #[inline]
1268    pub fn is_null<F>(mut self, field: F) -> Self
1269    where
1270        F: Into<FieldName>,
1271    {
1272        self.filters.push(Filter::IsNull(field.into()));
1273        self
1274    }
1275
1276    /// Add an IS NOT NULL filter.
1277    #[inline]
1278    pub fn is_not_null<F>(mut self, field: F) -> Self
1279    where
1280        F: Into<FieldName>,
1281    {
1282        self.filters.push(Filter::IsNotNull(field.into()));
1283        self
1284    }
1285
1286    /// Add a raw filter directly.
1287    #[inline]
1288    pub fn filter(mut self, filter: Filter) -> Self {
1289        if !filter.is_none() {
1290            self.filters.push(filter);
1291        }
1292        self
1293    }
1294
1295    /// Add a filter conditionally.
1296    #[inline]
1297    pub fn filter_if(self, condition: bool, filter: Filter) -> Self {
1298        if condition { self.filter(filter) } else { self }
1299    }
1300
1301    /// Add a filter conditionally if the option is Some.
1302    #[inline]
1303    pub fn filter_if_some<F>(self, opt: Option<F>) -> Self
1304    where
1305        F: Into<Filter>,
1306    {
1307        match opt {
1308            Some(f) => self.filter(f.into()),
1309            None => self,
1310        }
1311    }
1312
1313    /// Build an AND filter from all collected conditions.
1314    #[inline]
1315    pub fn build_and(self) -> Filter {
1316        let filters: Vec<_> = self.filters.into_iter().filter(|f| !f.is_none()).collect();
1317        match filters.len() {
1318            0 => Filter::None,
1319            1 => filters.into_iter().next().unwrap(),
1320            _ => Filter::And(filters.into_boxed_slice()),
1321        }
1322    }
1323
1324    /// Build an OR filter from all collected conditions.
1325    #[inline]
1326    pub fn build_or(self) -> Filter {
1327        let filters: Vec<_> = self.filters.into_iter().filter(|f| !f.is_none()).collect();
1328        match filters.len() {
1329            0 => Filter::None,
1330            1 => filters.into_iter().next().unwrap(),
1331            _ => Filter::Or(filters.into_boxed_slice()),
1332        }
1333    }
1334
1335    /// Get the current number of filters.
1336    #[inline]
1337    pub fn len(&self) -> usize {
1338        self.filters.len()
1339    }
1340
1341    /// Check if the builder is empty.
1342    #[inline]
1343    pub fn is_empty(&self) -> bool {
1344        self.filters.is_empty()
1345    }
1346}
1347
1348impl Default for FluentFilterBuilder {
1349    fn default() -> Self {
1350        Self::new()
1351    }
1352}
1353
1354impl Default for Filter {
1355    fn default() -> Self {
1356        Self::None
1357    }
1358}
1359
1360#[cfg(test)]
1361mod tests {
1362    use super::*;
1363
1364    #[test]
1365    fn test_filter_value_from() {
1366        assert_eq!(FilterValue::from(42i32), FilterValue::Int(42));
1367        assert_eq!(
1368            FilterValue::from("hello"),
1369            FilterValue::String("hello".to_string())
1370        );
1371        assert_eq!(FilterValue::from(true), FilterValue::Bool(true));
1372    }
1373
1374    #[test]
1375    fn test_scalar_filter_equals() {
1376        let filter = ScalarFilter::Equals("test@example.com".to_string()).into_filter("email");
1377
1378        let (sql, params) = filter.to_sql(0);
1379        assert_eq!(sql, "email = $1");
1380        assert_eq!(params.len(), 1);
1381    }
1382
1383    #[test]
1384    fn test_filter_and() {
1385        let f1 = Filter::Equals("name".into(), "Alice".into());
1386        let f2 = Filter::Gt("age".into(), FilterValue::Int(18));
1387        let combined = Filter::and([f1, f2]);
1388
1389        let (sql, params) = combined.to_sql(0);
1390        assert!(sql.contains("AND"));
1391        assert_eq!(params.len(), 2);
1392    }
1393
1394    #[test]
1395    fn test_filter_or() {
1396        let f1 = Filter::Equals("status".into(), "active".into());
1397        let f2 = Filter::Equals("status".into(), "pending".into());
1398        let combined = Filter::or([f1, f2]);
1399
1400        let (sql, _) = combined.to_sql(0);
1401        assert!(sql.contains("OR"));
1402    }
1403
1404    #[test]
1405    fn test_filter_not() {
1406        let filter = Filter::not(Filter::Equals("deleted".into(), FilterValue::Bool(true)));
1407
1408        let (sql, _) = filter.to_sql(0);
1409        assert!(sql.contains("NOT"));
1410    }
1411
1412    #[test]
1413    fn test_filter_is_null() {
1414        let filter = Filter::IsNull("deleted_at".into());
1415        let (sql, params) = filter.to_sql(0);
1416        assert_eq!(sql, "deleted_at IS NULL");
1417        assert!(params.is_empty());
1418    }
1419
1420    #[test]
1421    fn test_filter_in() {
1422        let filter = Filter::In("status".into(), vec!["active".into(), "pending".into()]);
1423        let (sql, params) = filter.to_sql(0);
1424        assert!(sql.contains("IN"));
1425        assert_eq!(params.len(), 2);
1426    }
1427
1428    #[test]
1429    fn test_filter_contains() {
1430        let filter = Filter::Contains("email".into(), "example".into());
1431        let (sql, params) = filter.to_sql(0);
1432        assert!(sql.contains("LIKE"));
1433        assert_eq!(params.len(), 1);
1434        if let FilterValue::String(s) = &params[0] {
1435            assert!(s.contains("%example%"));
1436        }
1437    }
1438
1439    // ==================== FilterValue Tests ====================
1440
1441    #[test]
1442    fn test_filter_value_is_null() {
1443        assert!(FilterValue::Null.is_null());
1444        assert!(!FilterValue::Bool(false).is_null());
1445        assert!(!FilterValue::Int(0).is_null());
1446        assert!(!FilterValue::Float(0.0).is_null());
1447        assert!(!FilterValue::String("".to_string()).is_null());
1448    }
1449
1450    #[test]
1451    fn test_filter_value_to_sql_placeholder() {
1452        let val = FilterValue::Int(42);
1453        assert_eq!(val.to_sql_placeholder(1), "$1");
1454        assert_eq!(val.to_sql_placeholder(10), "$10");
1455    }
1456
1457    #[test]
1458    fn test_filter_value_from_i64() {
1459        assert_eq!(FilterValue::from(42i64), FilterValue::Int(42));
1460        assert_eq!(FilterValue::from(-100i64), FilterValue::Int(-100));
1461    }
1462
1463    #[test]
1464    fn test_filter_value_from_f64() {
1465        assert_eq!(FilterValue::from(3.14f64), FilterValue::Float(3.14));
1466    }
1467
1468    #[test]
1469    fn test_filter_value_from_string() {
1470        assert_eq!(
1471            FilterValue::from("hello".to_string()),
1472            FilterValue::String("hello".to_string())
1473        );
1474    }
1475
1476    #[test]
1477    fn test_filter_value_from_vec() {
1478        let values: Vec<i32> = vec![1, 2, 3];
1479        let filter_val: FilterValue = values.into();
1480        if let FilterValue::List(list) = filter_val {
1481            assert_eq!(list.len(), 3);
1482            assert_eq!(list[0], FilterValue::Int(1));
1483            assert_eq!(list[1], FilterValue::Int(2));
1484            assert_eq!(list[2], FilterValue::Int(3));
1485        } else {
1486            panic!("Expected List");
1487        }
1488    }
1489
1490    #[test]
1491    fn test_filter_value_from_option_some() {
1492        let val: FilterValue = Some(42i32).into();
1493        assert_eq!(val, FilterValue::Int(42));
1494    }
1495
1496    #[test]
1497    fn test_filter_value_from_option_none() {
1498        let val: FilterValue = Option::<i32>::None.into();
1499        assert_eq!(val, FilterValue::Null);
1500    }
1501
1502    // ==================== ScalarFilter Tests ====================
1503
1504    #[test]
1505    fn test_scalar_filter_not() {
1506        let filter = ScalarFilter::Not(Box::new("test".to_string())).into_filter("name");
1507        let (sql, params) = filter.to_sql(0);
1508        assert_eq!(sql, "name != $1");
1509        assert_eq!(params.len(), 1);
1510    }
1511
1512    #[test]
1513    fn test_scalar_filter_in() {
1514        let filter = ScalarFilter::In(vec!["a".to_string(), "b".to_string()]).into_filter("status");
1515        let (sql, params) = filter.to_sql(0);
1516        assert!(sql.contains("IN"));
1517        assert_eq!(params.len(), 2);
1518    }
1519
1520    #[test]
1521    fn test_scalar_filter_not_in() {
1522        let filter = ScalarFilter::NotIn(vec!["x".to_string()]).into_filter("status");
1523        let (sql, params) = filter.to_sql(0);
1524        assert!(sql.contains("NOT IN"));
1525        assert_eq!(params.len(), 1);
1526    }
1527
1528    #[test]
1529    fn test_scalar_filter_lt() {
1530        let filter = ScalarFilter::Lt(100i32).into_filter("price");
1531        let (sql, params) = filter.to_sql(0);
1532        assert_eq!(sql, "price < $1");
1533        assert_eq!(params.len(), 1);
1534    }
1535
1536    #[test]
1537    fn test_scalar_filter_lte() {
1538        let filter = ScalarFilter::Lte(100i32).into_filter("price");
1539        let (sql, params) = filter.to_sql(0);
1540        assert_eq!(sql, "price <= $1");
1541        assert_eq!(params.len(), 1);
1542    }
1543
1544    #[test]
1545    fn test_scalar_filter_gt() {
1546        let filter = ScalarFilter::Gt(0i32).into_filter("quantity");
1547        let (sql, params) = filter.to_sql(0);
1548        assert_eq!(sql, "quantity > $1");
1549        assert_eq!(params.len(), 1);
1550    }
1551
1552    #[test]
1553    fn test_scalar_filter_gte() {
1554        let filter = ScalarFilter::Gte(0i32).into_filter("quantity");
1555        let (sql, params) = filter.to_sql(0);
1556        assert_eq!(sql, "quantity >= $1");
1557        assert_eq!(params.len(), 1);
1558    }
1559
1560    #[test]
1561    fn test_scalar_filter_starts_with() {
1562        let filter = ScalarFilter::StartsWith("prefix".to_string()).into_filter("name");
1563        let (sql, params) = filter.to_sql(0);
1564        assert!(sql.contains("LIKE"));
1565        assert_eq!(params.len(), 1);
1566        if let FilterValue::String(s) = &params[0] {
1567            assert!(s.starts_with("prefix"));
1568            assert!(s.ends_with("%"));
1569        }
1570    }
1571
1572    #[test]
1573    fn test_scalar_filter_ends_with() {
1574        let filter = ScalarFilter::EndsWith("suffix".to_string()).into_filter("name");
1575        let (sql, params) = filter.to_sql(0);
1576        assert!(sql.contains("LIKE"));
1577        assert_eq!(params.len(), 1);
1578        if let FilterValue::String(s) = &params[0] {
1579            assert!(s.starts_with("%"));
1580            assert!(s.ends_with("suffix"));
1581        }
1582    }
1583
1584    #[test]
1585    fn test_scalar_filter_is_null() {
1586        let filter = ScalarFilter::<String>::IsNull.into_filter("deleted_at");
1587        let (sql, params) = filter.to_sql(0);
1588        assert_eq!(sql, "deleted_at IS NULL");
1589        assert!(params.is_empty());
1590    }
1591
1592    #[test]
1593    fn test_scalar_filter_is_not_null() {
1594        let filter = ScalarFilter::<String>::IsNotNull.into_filter("name");
1595        let (sql, params) = filter.to_sql(0);
1596        assert_eq!(sql, "name IS NOT NULL");
1597        assert!(params.is_empty());
1598    }
1599
1600    // ==================== Filter Tests ====================
1601
1602    #[test]
1603    fn test_filter_none() {
1604        let filter = Filter::none();
1605        assert!(filter.is_none());
1606        let (sql, params) = filter.to_sql(0);
1607        assert_eq!(sql, "TRUE"); // Filter::None generates TRUE
1608        assert!(params.is_empty());
1609    }
1610
1611    #[test]
1612    fn test_filter_not_equals() {
1613        let filter = Filter::NotEquals("status".into(), "deleted".into());
1614        let (sql, params) = filter.to_sql(0);
1615        assert_eq!(sql, "status != $1");
1616        assert_eq!(params.len(), 1);
1617    }
1618
1619    #[test]
1620    fn test_filter_lte() {
1621        let filter = Filter::Lte("price".into(), FilterValue::Int(100));
1622        let (sql, params) = filter.to_sql(0);
1623        assert_eq!(sql, "price <= $1");
1624        assert_eq!(params.len(), 1);
1625    }
1626
1627    #[test]
1628    fn test_filter_gte() {
1629        let filter = Filter::Gte("quantity".into(), FilterValue::Int(0));
1630        let (sql, params) = filter.to_sql(0);
1631        assert_eq!(sql, "quantity >= $1");
1632        assert_eq!(params.len(), 1);
1633    }
1634
1635    #[test]
1636    fn test_filter_not_in() {
1637        let filter = Filter::NotIn("status".into(), vec!["deleted".into(), "archived".into()]);
1638        let (sql, params) = filter.to_sql(0);
1639        assert!(sql.contains("NOT IN"));
1640        assert_eq!(params.len(), 2);
1641    }
1642
1643    #[test]
1644    fn test_filter_starts_with() {
1645        let filter = Filter::StartsWith("email".into(), "admin".into());
1646        let (sql, params) = filter.to_sql(0);
1647        assert!(sql.contains("LIKE"));
1648        assert_eq!(params.len(), 1);
1649    }
1650
1651    #[test]
1652    fn test_filter_ends_with() {
1653        let filter = Filter::EndsWith("email".into(), "@example.com".into());
1654        let (sql, params) = filter.to_sql(0);
1655        assert!(sql.contains("LIKE"));
1656        assert_eq!(params.len(), 1);
1657    }
1658
1659    #[test]
1660    fn test_filter_is_not_null() {
1661        let filter = Filter::IsNotNull("name".into());
1662        let (sql, params) = filter.to_sql(0);
1663        assert_eq!(sql, "name IS NOT NULL");
1664        assert!(params.is_empty());
1665    }
1666
1667    // ==================== Filter Combination Tests ====================
1668
1669    #[test]
1670    fn test_filter_and_empty() {
1671        let filter = Filter::and([]);
1672        assert!(filter.is_none());
1673    }
1674
1675    #[test]
1676    fn test_filter_and_single() {
1677        let f = Filter::Equals("name".into(), "Alice".into());
1678        let combined = Filter::and([f.clone()]);
1679        assert_eq!(combined, f);
1680    }
1681
1682    #[test]
1683    fn test_filter_and_with_none() {
1684        let f1 = Filter::Equals("name".into(), "Alice".into());
1685        let f2 = Filter::None;
1686        let combined = Filter::and([f1.clone(), f2]);
1687        assert_eq!(combined, f1);
1688    }
1689
1690    #[test]
1691    fn test_filter_or_empty() {
1692        let filter = Filter::or([]);
1693        assert!(filter.is_none());
1694    }
1695
1696    #[test]
1697    fn test_filter_or_single() {
1698        let f = Filter::Equals("status".into(), "active".into());
1699        let combined = Filter::or([f.clone()]);
1700        assert_eq!(combined, f);
1701    }
1702
1703    #[test]
1704    fn test_filter_or_with_none() {
1705        let f1 = Filter::Equals("status".into(), "active".into());
1706        let f2 = Filter::None;
1707        let combined = Filter::or([f1.clone(), f2]);
1708        assert_eq!(combined, f1);
1709    }
1710
1711    #[test]
1712    fn test_filter_not_none() {
1713        let filter = Filter::not(Filter::None);
1714        assert!(filter.is_none());
1715    }
1716
1717    #[test]
1718    fn test_filter_and_then() {
1719        let f1 = Filter::Equals("name".into(), "Alice".into());
1720        let f2 = Filter::Gt("age".into(), FilterValue::Int(18));
1721        let combined = f1.and_then(f2);
1722
1723        let (sql, params) = combined.to_sql(0);
1724        assert!(sql.contains("AND"));
1725        assert_eq!(params.len(), 2);
1726    }
1727
1728    #[test]
1729    fn test_filter_and_then_with_none_first() {
1730        let f1 = Filter::None;
1731        let f2 = Filter::Equals("name".into(), "Bob".into());
1732        let combined = f1.and_then(f2.clone());
1733        assert_eq!(combined, f2);
1734    }
1735
1736    #[test]
1737    fn test_filter_and_then_with_none_second() {
1738        let f1 = Filter::Equals("name".into(), "Alice".into());
1739        let f2 = Filter::None;
1740        let combined = f1.clone().and_then(f2);
1741        assert_eq!(combined, f1);
1742    }
1743
1744    #[test]
1745    fn test_filter_and_then_chained() {
1746        let f1 = Filter::Equals("a".into(), "1".into());
1747        let f2 = Filter::Equals("b".into(), "2".into());
1748        let f3 = Filter::Equals("c".into(), "3".into());
1749        let combined = f1.and_then(f2).and_then(f3);
1750
1751        let (sql, params) = combined.to_sql(0);
1752        assert!(sql.contains("AND"));
1753        assert_eq!(params.len(), 3);
1754    }
1755
1756    #[test]
1757    fn test_filter_or_else() {
1758        let f1 = Filter::Equals("status".into(), "active".into());
1759        let f2 = Filter::Equals("status".into(), "pending".into());
1760        let combined = f1.or_else(f2);
1761
1762        let (sql, _) = combined.to_sql(0);
1763        assert!(sql.contains("OR"));
1764    }
1765
1766    #[test]
1767    fn test_filter_or_else_with_none_first() {
1768        let f1 = Filter::None;
1769        let f2 = Filter::Equals("name".into(), "Bob".into());
1770        let combined = f1.or_else(f2.clone());
1771        assert_eq!(combined, f2);
1772    }
1773
1774    #[test]
1775    fn test_filter_or_else_with_none_second() {
1776        let f1 = Filter::Equals("name".into(), "Alice".into());
1777        let f2 = Filter::None;
1778        let combined = f1.clone().or_else(f2);
1779        assert_eq!(combined, f1);
1780    }
1781
1782    // ==================== Complex Filter SQL Generation ====================
1783
1784    #[test]
1785    fn test_filter_nested_and_or() {
1786        let f1 = Filter::Equals("status".into(), "active".into());
1787        let f2 = Filter::and([
1788            Filter::Gt("age".into(), FilterValue::Int(18)),
1789            Filter::Lt("age".into(), FilterValue::Int(65)),
1790        ]);
1791        let combined = Filter::and([f1, f2]);
1792
1793        let (sql, params) = combined.to_sql(0);
1794        assert!(sql.contains("AND"));
1795        assert_eq!(params.len(), 3);
1796    }
1797
1798    #[test]
1799    fn test_filter_nested_not() {
1800        let inner = Filter::and([
1801            Filter::Equals("status".into(), "deleted".into()),
1802            Filter::Equals("archived".into(), FilterValue::Bool(true)),
1803        ]);
1804        let filter = Filter::not(inner);
1805
1806        let (sql, params) = filter.to_sql(0);
1807        assert!(sql.contains("NOT"));
1808        assert!(sql.contains("AND"));
1809        assert_eq!(params.len(), 2);
1810    }
1811
1812    #[test]
1813    fn test_filter_with_json_value() {
1814        let json_val = serde_json::json!({"key": "value"});
1815        let filter = Filter::Equals("metadata".into(), FilterValue::Json(json_val));
1816        let (sql, params) = filter.to_sql(0);
1817        assert_eq!(sql, "metadata = $1");
1818        assert_eq!(params.len(), 1);
1819    }
1820
1821    #[test]
1822    fn test_filter_in_empty_list() {
1823        let filter = Filter::In("status".into(), vec![]);
1824        let (sql, params) = filter.to_sql(0);
1825        // Empty IN generates FALSE (no match possible)
1826        assert!(
1827            sql.contains("FALSE")
1828                || sql.contains("1=0")
1829                || sql.is_empty()
1830                || sql.contains("status")
1831        );
1832        assert!(params.is_empty());
1833    }
1834
1835    #[test]
1836    fn test_filter_with_null_value() {
1837        // When filtering with Null value, it uses IS NULL instead of = $1
1838        let filter = Filter::IsNull("deleted_at".into());
1839        let (sql, params) = filter.to_sql(0);
1840        assert!(sql.contains("deleted_at"));
1841        assert!(sql.contains("IS NULL"));
1842        assert!(params.is_empty());
1843    }
1844
1845    // ==================== Builder Tests ====================
1846
1847    #[test]
1848    fn test_and_builder_basic() {
1849        let filter = Filter::and_builder(3)
1850            .push(Filter::Equals("active".into(), FilterValue::Bool(true)))
1851            .push(Filter::Gt("score".into(), FilterValue::Int(100)))
1852            .push(Filter::IsNotNull("email".into()))
1853            .build();
1854
1855        let (sql, params) = filter.to_sql(0);
1856        assert!(sql.contains("AND"));
1857        assert_eq!(params.len(), 2); // score and active, IS NOT NULL has no param
1858    }
1859
1860    #[test]
1861    fn test_and_builder_empty() {
1862        let filter = Filter::and_builder(0).build();
1863        assert!(filter.is_none());
1864    }
1865
1866    #[test]
1867    fn test_and_builder_single() {
1868        let filter = Filter::and_builder(1)
1869            .push(Filter::Equals("id".into(), FilterValue::Int(42)))
1870            .build();
1871
1872        // Single filter should not be wrapped in AND
1873        assert!(matches!(filter, Filter::Equals(_, _)));
1874    }
1875
1876    #[test]
1877    fn test_and_builder_filters_none() {
1878        let filter = Filter::and_builder(3)
1879            .push(Filter::None)
1880            .push(Filter::Equals("id".into(), FilterValue::Int(1)))
1881            .push(Filter::None)
1882            .build();
1883
1884        // None filters should be filtered out, leaving single filter
1885        assert!(matches!(filter, Filter::Equals(_, _)));
1886    }
1887
1888    #[test]
1889    fn test_and_builder_push_if() {
1890        let include_deleted = false;
1891        let filter = Filter::and_builder(2)
1892            .push(Filter::Equals("active".into(), FilterValue::Bool(true)))
1893            .push_if(include_deleted, Filter::IsNull("deleted_at".into()))
1894            .build();
1895
1896        // Should only have active filter since include_deleted is false
1897        assert!(matches!(filter, Filter::Equals(_, _)));
1898    }
1899
1900    #[test]
1901    fn test_or_builder_basic() {
1902        let filter = Filter::or_builder(2)
1903            .push(Filter::Equals(
1904                "role".into(),
1905                FilterValue::String("admin".into()),
1906            ))
1907            .push(Filter::Equals(
1908                "role".into(),
1909                FilterValue::String("moderator".into()),
1910            ))
1911            .build();
1912
1913        let (sql, _) = filter.to_sql(0);
1914        assert!(sql.contains("OR"));
1915    }
1916
1917    #[test]
1918    fn test_or_builder_empty() {
1919        let filter = Filter::or_builder(0).build();
1920        assert!(filter.is_none());
1921    }
1922
1923    #[test]
1924    fn test_or_builder_single() {
1925        let filter = Filter::or_builder(1)
1926            .push(Filter::Equals("id".into(), FilterValue::Int(42)))
1927            .build();
1928
1929        // Single filter should not be wrapped in OR
1930        assert!(matches!(filter, Filter::Equals(_, _)));
1931    }
1932
1933    #[test]
1934    fn test_fluent_builder_and() {
1935        let filter = Filter::builder()
1936            .eq("status", "active")
1937            .gt("age", 18)
1938            .is_not_null("email")
1939            .build_and();
1940
1941        let (sql, params) = filter.to_sql(0);
1942        assert!(sql.contains("AND"));
1943        assert_eq!(params.len(), 2);
1944    }
1945
1946    #[test]
1947    fn test_fluent_builder_or() {
1948        let filter = Filter::builder()
1949            .eq("role", "admin")
1950            .eq("role", "moderator")
1951            .build_or();
1952
1953        let (sql, _) = filter.to_sql(0);
1954        assert!(sql.contains("OR"));
1955    }
1956
1957    #[test]
1958    fn test_fluent_builder_with_capacity() {
1959        let filter = Filter::builder()
1960            .with_capacity(5)
1961            .eq("a", 1)
1962            .ne("b", 2)
1963            .lt("c", 3)
1964            .lte("d", 4)
1965            .gte("e", 5)
1966            .build_and();
1967
1968        let (sql, params) = filter.to_sql(0);
1969        assert!(sql.contains("AND"));
1970        assert_eq!(params.len(), 5);
1971    }
1972
1973    #[test]
1974    fn test_fluent_builder_string_operations() {
1975        let filter = Filter::builder()
1976            .contains("name", "john")
1977            .starts_with("email", "admin")
1978            .ends_with("domain", ".com")
1979            .build_and();
1980
1981        let (sql, _) = filter.to_sql(0);
1982        assert!(sql.contains("LIKE"));
1983    }
1984
1985    #[test]
1986    fn test_fluent_builder_null_operations() {
1987        let filter = Filter::builder()
1988            .is_null("deleted_at")
1989            .is_not_null("created_at")
1990            .build_and();
1991
1992        let (sql, _) = filter.to_sql(0);
1993        assert!(sql.contains("IS NULL"));
1994        assert!(sql.contains("IS NOT NULL"));
1995    }
1996
1997    #[test]
1998    fn test_fluent_builder_in_operations() {
1999        let filter = Filter::builder()
2000            .is_in("status", vec!["pending", "processing"])
2001            .not_in("role", vec!["banned", "suspended"])
2002            .build_and();
2003
2004        let (sql, _) = filter.to_sql(0);
2005        assert!(sql.contains("IN"));
2006        assert!(sql.contains("NOT IN"));
2007    }
2008
2009    #[test]
2010    fn test_fluent_builder_filter_if() {
2011        let include_archived = false;
2012        let filter = Filter::builder()
2013            .eq("active", true)
2014            .filter_if(
2015                include_archived,
2016                Filter::Equals("archived".into(), FilterValue::Bool(true)),
2017            )
2018            .build_and();
2019
2020        // Should only have active filter
2021        assert!(matches!(filter, Filter::Equals(_, _)));
2022    }
2023
2024    #[test]
2025    fn test_fluent_builder_filter_if_some() {
2026        let maybe_status: Option<Filter> = Some(Filter::Equals("status".into(), "active".into()));
2027        let filter = Filter::builder()
2028            .eq("id", 1)
2029            .filter_if_some(maybe_status)
2030            .build_and();
2031
2032        assert!(matches!(filter, Filter::And(_)));
2033    }
2034
2035    #[test]
2036    fn test_and_builder_extend() {
2037        let extra_filters = vec![
2038            Filter::Gt("score".into(), FilterValue::Int(100)),
2039            Filter::Lt("score".into(), FilterValue::Int(1000)),
2040        ];
2041
2042        let filter = Filter::and_builder(3)
2043            .push(Filter::Equals("active".into(), FilterValue::Bool(true)))
2044            .extend(extra_filters)
2045            .build();
2046
2047        let (sql, params) = filter.to_sql(0);
2048        assert!(sql.contains("AND"));
2049        assert_eq!(params.len(), 3);
2050    }
2051
2052    #[test]
2053    fn test_builder_len_and_is_empty() {
2054        let mut builder = AndFilterBuilder::new();
2055        assert!(builder.is_empty());
2056        assert_eq!(builder.len(), 0);
2057
2058        builder = builder.push(Filter::Equals("id".into(), FilterValue::Int(1)));
2059        assert!(!builder.is_empty());
2060        assert_eq!(builder.len(), 1);
2061    }
2062
2063    // ==================== and2/or2 Tests ====================
2064
2065    #[test]
2066    fn test_and2_both_valid() {
2067        let a = Filter::Equals("id".into(), FilterValue::Int(1));
2068        let b = Filter::Equals("active".into(), FilterValue::Bool(true));
2069        let filter = Filter::and2(a, b);
2070
2071        assert!(matches!(filter, Filter::And(_)));
2072        let (sql, params) = filter.to_sql(0);
2073        assert!(sql.contains("AND"));
2074        assert_eq!(params.len(), 2);
2075    }
2076
2077    #[test]
2078    fn test_and2_first_none() {
2079        let a = Filter::None;
2080        let b = Filter::Equals("active".into(), FilterValue::Bool(true));
2081        let filter = Filter::and2(a, b.clone());
2082
2083        assert_eq!(filter, b);
2084    }
2085
2086    #[test]
2087    fn test_and2_second_none() {
2088        let a = Filter::Equals("id".into(), FilterValue::Int(1));
2089        let b = Filter::None;
2090        let filter = Filter::and2(a.clone(), b);
2091
2092        assert_eq!(filter, a);
2093    }
2094
2095    #[test]
2096    fn test_and2_both_none() {
2097        let filter = Filter::and2(Filter::None, Filter::None);
2098        assert!(filter.is_none());
2099    }
2100
2101    #[test]
2102    fn test_or2_both_valid() {
2103        let a = Filter::Equals("role".into(), FilterValue::String("admin".into()));
2104        let b = Filter::Equals("role".into(), FilterValue::String("mod".into()));
2105        let filter = Filter::or2(a, b);
2106
2107        assert!(matches!(filter, Filter::Or(_)));
2108        let (sql, _) = filter.to_sql(0);
2109        assert!(sql.contains("OR"));
2110    }
2111
2112    #[test]
2113    fn test_or2_first_none() {
2114        let a = Filter::None;
2115        let b = Filter::Equals("active".into(), FilterValue::Bool(true));
2116        let filter = Filter::or2(a, b.clone());
2117
2118        assert_eq!(filter, b);
2119    }
2120
2121    #[test]
2122    fn test_or2_second_none() {
2123        let a = Filter::Equals("id".into(), FilterValue::Int(1));
2124        let b = Filter::None;
2125        let filter = Filter::or2(a.clone(), b);
2126
2127        assert_eq!(filter, a);
2128    }
2129
2130    #[test]
2131    fn test_or2_both_none() {
2132        let filter = Filter::or2(Filter::None, Filter::None);
2133        assert!(filter.is_none());
2134    }
2135}