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