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
1358#[cfg(test)]
1359mod tests {
1360    use super::*;
1361
1362    #[test]
1363    fn test_filter_value_from() {
1364        assert_eq!(FilterValue::from(42i32), FilterValue::Int(42));
1365        assert_eq!(
1366            FilterValue::from("hello"),
1367            FilterValue::String("hello".to_string())
1368        );
1369        assert_eq!(FilterValue::from(true), FilterValue::Bool(true));
1370    }
1371
1372    #[test]
1373    fn test_scalar_filter_equals() {
1374        let filter = ScalarFilter::Equals("test@example.com".to_string()).into_filter("email");
1375
1376        let (sql, params) = filter.to_sql(0);
1377        assert_eq!(sql, "email = $1");
1378        assert_eq!(params.len(), 1);
1379    }
1380
1381    #[test]
1382    fn test_filter_and() {
1383        let f1 = Filter::Equals("name".into(), "Alice".into());
1384        let f2 = Filter::Gt("age".into(), FilterValue::Int(18));
1385        let combined = Filter::and([f1, f2]);
1386
1387        let (sql, params) = combined.to_sql(0);
1388        assert!(sql.contains("AND"));
1389        assert_eq!(params.len(), 2);
1390    }
1391
1392    #[test]
1393    fn test_filter_or() {
1394        let f1 = Filter::Equals("status".into(), "active".into());
1395        let f2 = Filter::Equals("status".into(), "pending".into());
1396        let combined = Filter::or([f1, f2]);
1397
1398        let (sql, _) = combined.to_sql(0);
1399        assert!(sql.contains("OR"));
1400    }
1401
1402    #[test]
1403    fn test_filter_not() {
1404        let filter = Filter::not(Filter::Equals("deleted".into(), FilterValue::Bool(true)));
1405
1406        let (sql, _) = filter.to_sql(0);
1407        assert!(sql.contains("NOT"));
1408    }
1409
1410    #[test]
1411    fn test_filter_is_null() {
1412        let filter = Filter::IsNull("deleted_at".into());
1413        let (sql, params) = filter.to_sql(0);
1414        assert_eq!(sql, "deleted_at IS NULL");
1415        assert!(params.is_empty());
1416    }
1417
1418    #[test]
1419    fn test_filter_in() {
1420        let filter = Filter::In("status".into(), vec!["active".into(), "pending".into()]);
1421        let (sql, params) = filter.to_sql(0);
1422        assert!(sql.contains("IN"));
1423        assert_eq!(params.len(), 2);
1424    }
1425
1426    #[test]
1427    fn test_filter_contains() {
1428        let filter = Filter::Contains("email".into(), "example".into());
1429        let (sql, params) = filter.to_sql(0);
1430        assert!(sql.contains("LIKE"));
1431        assert_eq!(params.len(), 1);
1432        if let FilterValue::String(s) = &params[0] {
1433            assert!(s.contains("%example%"));
1434        }
1435    }
1436
1437    // ==================== FilterValue Tests ====================
1438
1439    #[test]
1440    fn test_filter_value_is_null() {
1441        assert!(FilterValue::Null.is_null());
1442        assert!(!FilterValue::Bool(false).is_null());
1443        assert!(!FilterValue::Int(0).is_null());
1444        assert!(!FilterValue::Float(0.0).is_null());
1445        assert!(!FilterValue::String("".to_string()).is_null());
1446    }
1447
1448    #[test]
1449    fn test_filter_value_to_sql_placeholder() {
1450        let val = FilterValue::Int(42);
1451        assert_eq!(val.to_sql_placeholder(1), "$1");
1452        assert_eq!(val.to_sql_placeholder(10), "$10");
1453    }
1454
1455    #[test]
1456    fn test_filter_value_from_i64() {
1457        assert_eq!(FilterValue::from(42i64), FilterValue::Int(42));
1458        assert_eq!(FilterValue::from(-100i64), FilterValue::Int(-100));
1459    }
1460
1461    #[test]
1462    fn test_filter_value_from_f64() {
1463        assert_eq!(FilterValue::from(3.14f64), FilterValue::Float(3.14));
1464    }
1465
1466    #[test]
1467    fn test_filter_value_from_string() {
1468        assert_eq!(
1469            FilterValue::from("hello".to_string()),
1470            FilterValue::String("hello".to_string())
1471        );
1472    }
1473
1474    #[test]
1475    fn test_filter_value_from_vec() {
1476        let values: Vec<i32> = vec![1, 2, 3];
1477        let filter_val: FilterValue = values.into();
1478        if let FilterValue::List(list) = filter_val {
1479            assert_eq!(list.len(), 3);
1480            assert_eq!(list[0], FilterValue::Int(1));
1481            assert_eq!(list[1], FilterValue::Int(2));
1482            assert_eq!(list[2], FilterValue::Int(3));
1483        } else {
1484            panic!("Expected List");
1485        }
1486    }
1487
1488    #[test]
1489    fn test_filter_value_from_option_some() {
1490        let val: FilterValue = Some(42i32).into();
1491        assert_eq!(val, FilterValue::Int(42));
1492    }
1493
1494    #[test]
1495    fn test_filter_value_from_option_none() {
1496        let val: FilterValue = Option::<i32>::None.into();
1497        assert_eq!(val, FilterValue::Null);
1498    }
1499
1500    // ==================== ScalarFilter Tests ====================
1501
1502    #[test]
1503    fn test_scalar_filter_not() {
1504        let filter = ScalarFilter::Not(Box::new("test".to_string())).into_filter("name");
1505        let (sql, params) = filter.to_sql(0);
1506        assert_eq!(sql, "name != $1");
1507        assert_eq!(params.len(), 1);
1508    }
1509
1510    #[test]
1511    fn test_scalar_filter_in() {
1512        let filter = ScalarFilter::In(vec!["a".to_string(), "b".to_string()]).into_filter("status");
1513        let (sql, params) = filter.to_sql(0);
1514        assert!(sql.contains("IN"));
1515        assert_eq!(params.len(), 2);
1516    }
1517
1518    #[test]
1519    fn test_scalar_filter_not_in() {
1520        let filter = ScalarFilter::NotIn(vec!["x".to_string()]).into_filter("status");
1521        let (sql, params) = filter.to_sql(0);
1522        assert!(sql.contains("NOT IN"));
1523        assert_eq!(params.len(), 1);
1524    }
1525
1526    #[test]
1527    fn test_scalar_filter_lt() {
1528        let filter = ScalarFilter::Lt(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_lte() {
1536        let filter = ScalarFilter::Lte(100i32).into_filter("price");
1537        let (sql, params) = filter.to_sql(0);
1538        assert_eq!(sql, "price <= $1");
1539        assert_eq!(params.len(), 1);
1540    }
1541
1542    #[test]
1543    fn test_scalar_filter_gt() {
1544        let filter = ScalarFilter::Gt(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_gte() {
1552        let filter = ScalarFilter::Gte(0i32).into_filter("quantity");
1553        let (sql, params) = filter.to_sql(0);
1554        assert_eq!(sql, "quantity >= $1");
1555        assert_eq!(params.len(), 1);
1556    }
1557
1558    #[test]
1559    fn test_scalar_filter_starts_with() {
1560        let filter = ScalarFilter::StartsWith("prefix".to_string()).into_filter("name");
1561        let (sql, params) = filter.to_sql(0);
1562        assert!(sql.contains("LIKE"));
1563        assert_eq!(params.len(), 1);
1564        if let FilterValue::String(s) = &params[0] {
1565            assert!(s.starts_with("prefix"));
1566            assert!(s.ends_with("%"));
1567        }
1568    }
1569
1570    #[test]
1571    fn test_scalar_filter_ends_with() {
1572        let filter = ScalarFilter::EndsWith("suffix".to_string()).into_filter("name");
1573        let (sql, params) = filter.to_sql(0);
1574        assert!(sql.contains("LIKE"));
1575        assert_eq!(params.len(), 1);
1576        if let FilterValue::String(s) = &params[0] {
1577            assert!(s.starts_with("%"));
1578            assert!(s.ends_with("suffix"));
1579        }
1580    }
1581
1582    #[test]
1583    fn test_scalar_filter_is_null() {
1584        let filter = ScalarFilter::<String>::IsNull.into_filter("deleted_at");
1585        let (sql, params) = filter.to_sql(0);
1586        assert_eq!(sql, "deleted_at IS NULL");
1587        assert!(params.is_empty());
1588    }
1589
1590    #[test]
1591    fn test_scalar_filter_is_not_null() {
1592        let filter = ScalarFilter::<String>::IsNotNull.into_filter("name");
1593        let (sql, params) = filter.to_sql(0);
1594        assert_eq!(sql, "name IS NOT NULL");
1595        assert!(params.is_empty());
1596    }
1597
1598    // ==================== Filter Tests ====================
1599
1600    #[test]
1601    fn test_filter_none() {
1602        let filter = Filter::none();
1603        assert!(filter.is_none());
1604        let (sql, params) = filter.to_sql(0);
1605        assert_eq!(sql, "TRUE"); // Filter::None generates TRUE
1606        assert!(params.is_empty());
1607    }
1608
1609    #[test]
1610    fn test_filter_not_equals() {
1611        let filter = Filter::NotEquals("status".into(), "deleted".into());
1612        let (sql, params) = filter.to_sql(0);
1613        assert_eq!(sql, "status != $1");
1614        assert_eq!(params.len(), 1);
1615    }
1616
1617    #[test]
1618    fn test_filter_lte() {
1619        let filter = Filter::Lte("price".into(), FilterValue::Int(100));
1620        let (sql, params) = filter.to_sql(0);
1621        assert_eq!(sql, "price <= $1");
1622        assert_eq!(params.len(), 1);
1623    }
1624
1625    #[test]
1626    fn test_filter_gte() {
1627        let filter = Filter::Gte("quantity".into(), FilterValue::Int(0));
1628        let (sql, params) = filter.to_sql(0);
1629        assert_eq!(sql, "quantity >= $1");
1630        assert_eq!(params.len(), 1);
1631    }
1632
1633    #[test]
1634    fn test_filter_not_in() {
1635        let filter = Filter::NotIn("status".into(), vec!["deleted".into(), "archived".into()]);
1636        let (sql, params) = filter.to_sql(0);
1637        assert!(sql.contains("NOT IN"));
1638        assert_eq!(params.len(), 2);
1639    }
1640
1641    #[test]
1642    fn test_filter_starts_with() {
1643        let filter = Filter::StartsWith("email".into(), "admin".into());
1644        let (sql, params) = filter.to_sql(0);
1645        assert!(sql.contains("LIKE"));
1646        assert_eq!(params.len(), 1);
1647    }
1648
1649    #[test]
1650    fn test_filter_ends_with() {
1651        let filter = Filter::EndsWith("email".into(), "@example.com".into());
1652        let (sql, params) = filter.to_sql(0);
1653        assert!(sql.contains("LIKE"));
1654        assert_eq!(params.len(), 1);
1655    }
1656
1657    #[test]
1658    fn test_filter_is_not_null() {
1659        let filter = Filter::IsNotNull("name".into());
1660        let (sql, params) = filter.to_sql(0);
1661        assert_eq!(sql, "name IS NOT NULL");
1662        assert!(params.is_empty());
1663    }
1664
1665    // ==================== Filter Combination Tests ====================
1666
1667    #[test]
1668    fn test_filter_and_empty() {
1669        let filter = Filter::and([]);
1670        assert!(filter.is_none());
1671    }
1672
1673    #[test]
1674    fn test_filter_and_single() {
1675        let f = Filter::Equals("name".into(), "Alice".into());
1676        let combined = Filter::and([f.clone()]);
1677        assert_eq!(combined, f);
1678    }
1679
1680    #[test]
1681    fn test_filter_and_with_none() {
1682        let f1 = Filter::Equals("name".into(), "Alice".into());
1683        let f2 = Filter::None;
1684        let combined = Filter::and([f1.clone(), f2]);
1685        assert_eq!(combined, f1);
1686    }
1687
1688    #[test]
1689    fn test_filter_or_empty() {
1690        let filter = Filter::or([]);
1691        assert!(filter.is_none());
1692    }
1693
1694    #[test]
1695    fn test_filter_or_single() {
1696        let f = Filter::Equals("status".into(), "active".into());
1697        let combined = Filter::or([f.clone()]);
1698        assert_eq!(combined, f);
1699    }
1700
1701    #[test]
1702    fn test_filter_or_with_none() {
1703        let f1 = Filter::Equals("status".into(), "active".into());
1704        let f2 = Filter::None;
1705        let combined = Filter::or([f1.clone(), f2]);
1706        assert_eq!(combined, f1);
1707    }
1708
1709    #[test]
1710    fn test_filter_not_none() {
1711        let filter = Filter::not(Filter::None);
1712        assert!(filter.is_none());
1713    }
1714
1715    #[test]
1716    fn test_filter_and_then() {
1717        let f1 = Filter::Equals("name".into(), "Alice".into());
1718        let f2 = Filter::Gt("age".into(), FilterValue::Int(18));
1719        let combined = f1.and_then(f2);
1720
1721        let (sql, params) = combined.to_sql(0);
1722        assert!(sql.contains("AND"));
1723        assert_eq!(params.len(), 2);
1724    }
1725
1726    #[test]
1727    fn test_filter_and_then_with_none_first() {
1728        let f1 = Filter::None;
1729        let f2 = Filter::Equals("name".into(), "Bob".into());
1730        let combined = f1.and_then(f2.clone());
1731        assert_eq!(combined, f2);
1732    }
1733
1734    #[test]
1735    fn test_filter_and_then_with_none_second() {
1736        let f1 = Filter::Equals("name".into(), "Alice".into());
1737        let f2 = Filter::None;
1738        let combined = f1.clone().and_then(f2);
1739        assert_eq!(combined, f1);
1740    }
1741
1742    #[test]
1743    fn test_filter_and_then_chained() {
1744        let f1 = Filter::Equals("a".into(), "1".into());
1745        let f2 = Filter::Equals("b".into(), "2".into());
1746        let f3 = Filter::Equals("c".into(), "3".into());
1747        let combined = f1.and_then(f2).and_then(f3);
1748
1749        let (sql, params) = combined.to_sql(0);
1750        assert!(sql.contains("AND"));
1751        assert_eq!(params.len(), 3);
1752    }
1753
1754    #[test]
1755    fn test_filter_or_else() {
1756        let f1 = Filter::Equals("status".into(), "active".into());
1757        let f2 = Filter::Equals("status".into(), "pending".into());
1758        let combined = f1.or_else(f2);
1759
1760        let (sql, _) = combined.to_sql(0);
1761        assert!(sql.contains("OR"));
1762    }
1763
1764    #[test]
1765    fn test_filter_or_else_with_none_first() {
1766        let f1 = Filter::None;
1767        let f2 = Filter::Equals("name".into(), "Bob".into());
1768        let combined = f1.or_else(f2.clone());
1769        assert_eq!(combined, f2);
1770    }
1771
1772    #[test]
1773    fn test_filter_or_else_with_none_second() {
1774        let f1 = Filter::Equals("name".into(), "Alice".into());
1775        let f2 = Filter::None;
1776        let combined = f1.clone().or_else(f2);
1777        assert_eq!(combined, f1);
1778    }
1779
1780    // ==================== Complex Filter SQL Generation ====================
1781
1782    #[test]
1783    fn test_filter_nested_and_or() {
1784        let f1 = Filter::Equals("status".into(), "active".into());
1785        let f2 = Filter::and([
1786            Filter::Gt("age".into(), FilterValue::Int(18)),
1787            Filter::Lt("age".into(), FilterValue::Int(65)),
1788        ]);
1789        let combined = Filter::and([f1, f2]);
1790
1791        let (sql, params) = combined.to_sql(0);
1792        assert!(sql.contains("AND"));
1793        assert_eq!(params.len(), 3);
1794    }
1795
1796    #[test]
1797    fn test_filter_nested_not() {
1798        let inner = Filter::and([
1799            Filter::Equals("status".into(), "deleted".into()),
1800            Filter::Equals("archived".into(), FilterValue::Bool(true)),
1801        ]);
1802        let filter = Filter::not(inner);
1803
1804        let (sql, params) = filter.to_sql(0);
1805        assert!(sql.contains("NOT"));
1806        assert!(sql.contains("AND"));
1807        assert_eq!(params.len(), 2);
1808    }
1809
1810    #[test]
1811    fn test_filter_with_json_value() {
1812        let json_val = serde_json::json!({"key": "value"});
1813        let filter = Filter::Equals("metadata".into(), FilterValue::Json(json_val));
1814        let (sql, params) = filter.to_sql(0);
1815        assert_eq!(sql, "metadata = $1");
1816        assert_eq!(params.len(), 1);
1817    }
1818
1819    #[test]
1820    fn test_filter_in_empty_list() {
1821        let filter = Filter::In("status".into(), vec![]);
1822        let (sql, params) = filter.to_sql(0);
1823        // Empty IN generates FALSE (no match possible)
1824        assert!(
1825            sql.contains("FALSE")
1826                || sql.contains("1=0")
1827                || sql.is_empty()
1828                || sql.contains("status")
1829        );
1830        assert!(params.is_empty());
1831    }
1832
1833    #[test]
1834    fn test_filter_with_null_value() {
1835        // When filtering with Null value, it uses IS NULL instead of = $1
1836        let filter = Filter::IsNull("deleted_at".into());
1837        let (sql, params) = filter.to_sql(0);
1838        assert!(sql.contains("deleted_at"));
1839        assert!(sql.contains("IS NULL"));
1840        assert!(params.is_empty());
1841    }
1842
1843    // ==================== Builder Tests ====================
1844
1845    #[test]
1846    fn test_and_builder_basic() {
1847        let filter = Filter::and_builder(3)
1848            .push(Filter::Equals("active".into(), FilterValue::Bool(true)))
1849            .push(Filter::Gt("score".into(), FilterValue::Int(100)))
1850            .push(Filter::IsNotNull("email".into()))
1851            .build();
1852
1853        let (sql, params) = filter.to_sql(0);
1854        assert!(sql.contains("AND"));
1855        assert_eq!(params.len(), 2); // score and active, IS NOT NULL has no param
1856    }
1857
1858    #[test]
1859    fn test_and_builder_empty() {
1860        let filter = Filter::and_builder(0).build();
1861        assert!(filter.is_none());
1862    }
1863
1864    #[test]
1865    fn test_and_builder_single() {
1866        let filter = Filter::and_builder(1)
1867            .push(Filter::Equals("id".into(), FilterValue::Int(42)))
1868            .build();
1869
1870        // Single filter should not be wrapped in AND
1871        assert!(matches!(filter, Filter::Equals(_, _)));
1872    }
1873
1874    #[test]
1875    fn test_and_builder_filters_none() {
1876        let filter = Filter::and_builder(3)
1877            .push(Filter::None)
1878            .push(Filter::Equals("id".into(), FilterValue::Int(1)))
1879            .push(Filter::None)
1880            .build();
1881
1882        // None filters should be filtered out, leaving single filter
1883        assert!(matches!(filter, Filter::Equals(_, _)));
1884    }
1885
1886    #[test]
1887    fn test_and_builder_push_if() {
1888        let include_deleted = false;
1889        let filter = Filter::and_builder(2)
1890            .push(Filter::Equals("active".into(), FilterValue::Bool(true)))
1891            .push_if(include_deleted, Filter::IsNull("deleted_at".into()))
1892            .build();
1893
1894        // Should only have active filter since include_deleted is false
1895        assert!(matches!(filter, Filter::Equals(_, _)));
1896    }
1897
1898    #[test]
1899    fn test_or_builder_basic() {
1900        let filter = Filter::or_builder(2)
1901            .push(Filter::Equals(
1902                "role".into(),
1903                FilterValue::String("admin".into()),
1904            ))
1905            .push(Filter::Equals(
1906                "role".into(),
1907                FilterValue::String("moderator".into()),
1908            ))
1909            .build();
1910
1911        let (sql, _) = filter.to_sql(0);
1912        assert!(sql.contains("OR"));
1913    }
1914
1915    #[test]
1916    fn test_or_builder_empty() {
1917        let filter = Filter::or_builder(0).build();
1918        assert!(filter.is_none());
1919    }
1920
1921    #[test]
1922    fn test_or_builder_single() {
1923        let filter = Filter::or_builder(1)
1924            .push(Filter::Equals("id".into(), FilterValue::Int(42)))
1925            .build();
1926
1927        // Single filter should not be wrapped in OR
1928        assert!(matches!(filter, Filter::Equals(_, _)));
1929    }
1930
1931    #[test]
1932    fn test_fluent_builder_and() {
1933        let filter = Filter::builder()
1934            .eq("status", "active")
1935            .gt("age", 18)
1936            .is_not_null("email")
1937            .build_and();
1938
1939        let (sql, params) = filter.to_sql(0);
1940        assert!(sql.contains("AND"));
1941        assert_eq!(params.len(), 2);
1942    }
1943
1944    #[test]
1945    fn test_fluent_builder_or() {
1946        let filter = Filter::builder()
1947            .eq("role", "admin")
1948            .eq("role", "moderator")
1949            .build_or();
1950
1951        let (sql, _) = filter.to_sql(0);
1952        assert!(sql.contains("OR"));
1953    }
1954
1955    #[test]
1956    fn test_fluent_builder_with_capacity() {
1957        let filter = Filter::builder()
1958            .with_capacity(5)
1959            .eq("a", 1)
1960            .ne("b", 2)
1961            .lt("c", 3)
1962            .lte("d", 4)
1963            .gte("e", 5)
1964            .build_and();
1965
1966        let (sql, params) = filter.to_sql(0);
1967        assert!(sql.contains("AND"));
1968        assert_eq!(params.len(), 5);
1969    }
1970
1971    #[test]
1972    fn test_fluent_builder_string_operations() {
1973        let filter = Filter::builder()
1974            .contains("name", "john")
1975            .starts_with("email", "admin")
1976            .ends_with("domain", ".com")
1977            .build_and();
1978
1979        let (sql, _) = filter.to_sql(0);
1980        assert!(sql.contains("LIKE"));
1981    }
1982
1983    #[test]
1984    fn test_fluent_builder_null_operations() {
1985        let filter = Filter::builder()
1986            .is_null("deleted_at")
1987            .is_not_null("created_at")
1988            .build_and();
1989
1990        let (sql, _) = filter.to_sql(0);
1991        assert!(sql.contains("IS NULL"));
1992        assert!(sql.contains("IS NOT NULL"));
1993    }
1994
1995    #[test]
1996    fn test_fluent_builder_in_operations() {
1997        let filter = Filter::builder()
1998            .is_in("status", vec!["pending", "processing"])
1999            .not_in("role", vec!["banned", "suspended"])
2000            .build_and();
2001
2002        let (sql, _) = filter.to_sql(0);
2003        assert!(sql.contains("IN"));
2004        assert!(sql.contains("NOT IN"));
2005    }
2006
2007    #[test]
2008    fn test_fluent_builder_filter_if() {
2009        let include_archived = false;
2010        let filter = Filter::builder()
2011            .eq("active", true)
2012            .filter_if(
2013                include_archived,
2014                Filter::Equals("archived".into(), FilterValue::Bool(true)),
2015            )
2016            .build_and();
2017
2018        // Should only have active filter
2019        assert!(matches!(filter, Filter::Equals(_, _)));
2020    }
2021
2022    #[test]
2023    fn test_fluent_builder_filter_if_some() {
2024        let maybe_status: Option<Filter> = Some(Filter::Equals("status".into(), "active".into()));
2025        let filter = Filter::builder()
2026            .eq("id", 1)
2027            .filter_if_some(maybe_status)
2028            .build_and();
2029
2030        assert!(matches!(filter, Filter::And(_)));
2031    }
2032
2033    #[test]
2034    fn test_and_builder_extend() {
2035        let extra_filters = vec![
2036            Filter::Gt("score".into(), FilterValue::Int(100)),
2037            Filter::Lt("score".into(), FilterValue::Int(1000)),
2038        ];
2039
2040        let filter = Filter::and_builder(3)
2041            .push(Filter::Equals("active".into(), FilterValue::Bool(true)))
2042            .extend(extra_filters)
2043            .build();
2044
2045        let (sql, params) = filter.to_sql(0);
2046        assert!(sql.contains("AND"));
2047        assert_eq!(params.len(), 3);
2048    }
2049
2050    #[test]
2051    fn test_builder_len_and_is_empty() {
2052        let mut builder = AndFilterBuilder::new();
2053        assert!(builder.is_empty());
2054        assert_eq!(builder.len(), 0);
2055
2056        builder = builder.push(Filter::Equals("id".into(), FilterValue::Int(1)));
2057        assert!(!builder.is_empty());
2058        assert_eq!(builder.len(), 1);
2059    }
2060
2061    // ==================== and2/or2 Tests ====================
2062
2063    #[test]
2064    fn test_and2_both_valid() {
2065        let a = Filter::Equals("id".into(), FilterValue::Int(1));
2066        let b = Filter::Equals("active".into(), FilterValue::Bool(true));
2067        let filter = Filter::and2(a, b);
2068
2069        assert!(matches!(filter, Filter::And(_)));
2070        let (sql, params) = filter.to_sql(0);
2071        assert!(sql.contains("AND"));
2072        assert_eq!(params.len(), 2);
2073    }
2074
2075    #[test]
2076    fn test_and2_first_none() {
2077        let a = Filter::None;
2078        let b = Filter::Equals("active".into(), FilterValue::Bool(true));
2079        let filter = Filter::and2(a, b.clone());
2080
2081        assert_eq!(filter, b);
2082    }
2083
2084    #[test]
2085    fn test_and2_second_none() {
2086        let a = Filter::Equals("id".into(), FilterValue::Int(1));
2087        let b = Filter::None;
2088        let filter = Filter::and2(a.clone(), b);
2089
2090        assert_eq!(filter, a);
2091    }
2092
2093    #[test]
2094    fn test_and2_both_none() {
2095        let filter = Filter::and2(Filter::None, Filter::None);
2096        assert!(filter.is_none());
2097    }
2098
2099    #[test]
2100    fn test_or2_both_valid() {
2101        let a = Filter::Equals("role".into(), FilterValue::String("admin".into()));
2102        let b = Filter::Equals("role".into(), FilterValue::String("mod".into()));
2103        let filter = Filter::or2(a, b);
2104
2105        assert!(matches!(filter, Filter::Or(_)));
2106        let (sql, _) = filter.to_sql(0);
2107        assert!(sql.contains("OR"));
2108    }
2109
2110    #[test]
2111    fn test_or2_first_none() {
2112        let a = Filter::None;
2113        let b = Filter::Equals("active".into(), FilterValue::Bool(true));
2114        let filter = Filter::or2(a, b.clone());
2115
2116        assert_eq!(filter, b);
2117    }
2118
2119    #[test]
2120    fn test_or2_second_none() {
2121        let a = Filter::Equals("id".into(), FilterValue::Int(1));
2122        let b = Filter::None;
2123        let filter = Filter::or2(a.clone(), b);
2124
2125        assert_eq!(filter, a);
2126    }
2127
2128    #[test]
2129    fn test_or2_both_none() {
2130        let filter = Filter::or2(Filter::None, Filter::None);
2131        assert!(filter.is_none());
2132    }
2133}