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