Skip to main content

reddb_engine/query/
filter.rs

1//! Filter Predicates for Query Engine
2//!
3//! Provides filter operations for querying data including comparison,
4//! logical, and pattern matching operations.
5
6use super::value_compare::partial_compare_values;
7use reddb_types::Value;
8use std::fmt;
9
10/// Filter operation type
11#[derive(Debug, Clone, PartialEq)]
12pub enum FilterOp {
13    /// Equal to
14    Eq,
15    /// Not equal to
16    Ne,
17    /// Less than
18    Lt,
19    /// Less than or equal
20    Le,
21    /// Greater than
22    Gt,
23    /// Greater than or equal
24    Ge,
25    /// Between two values (inclusive)
26    Between,
27    /// In a set of values
28    In,
29    /// Not in a set of values
30    NotIn,
31    /// Like pattern matching (SQL-style with % and _)
32    Like,
33    /// Not like pattern
34    NotLike,
35    /// Is null
36    IsNull,
37    /// Is not null
38    IsNotNull,
39    /// Contains (for text or arrays)
40    Contains,
41    /// Starts with
42    StartsWith,
43    /// Ends with
44    EndsWith,
45}
46
47impl fmt::Display for FilterOp {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        match self {
50            FilterOp::Eq => write!(f, "="),
51            FilterOp::Ne => write!(f, "!="),
52            FilterOp::Lt => write!(f, "<"),
53            FilterOp::Le => write!(f, "<="),
54            FilterOp::Gt => write!(f, ">"),
55            FilterOp::Ge => write!(f, ">="),
56            FilterOp::Between => write!(f, "BETWEEN"),
57            FilterOp::In => write!(f, "IN"),
58            FilterOp::NotIn => write!(f, "NOT IN"),
59            FilterOp::Like => write!(f, "LIKE"),
60            FilterOp::NotLike => write!(f, "NOT LIKE"),
61            FilterOp::IsNull => write!(f, "IS NULL"),
62            FilterOp::IsNotNull => write!(f, "IS NOT NULL"),
63            FilterOp::Contains => write!(f, "CONTAINS"),
64            FilterOp::StartsWith => write!(f, "STARTS WITH"),
65            FilterOp::EndsWith => write!(f, "ENDS WITH"),
66        }
67    }
68}
69
70/// A single predicate condition
71#[derive(Debug, Clone)]
72pub struct Predicate {
73    /// Column name
74    pub column: String,
75    /// Filter operation
76    pub op: FilterOp,
77    /// Value(s) to compare against
78    pub value: PredicateValue,
79}
80
81/// Value type for predicates
82#[derive(Debug, Clone)]
83pub enum PredicateValue {
84    /// Single value
85    Single(Value),
86    /// Range of values (for BETWEEN)
87    Range(Value, Value),
88    /// List of values (for IN)
89    List(Vec<Value>),
90    /// Pattern string (for LIKE)
91    Pattern(String),
92    /// No value (for IS NULL)
93    None,
94}
95
96impl Predicate {
97    /// Create an equality predicate
98    pub fn eq(column: impl Into<String>, value: Value) -> Self {
99        Self {
100            column: column.into(),
101            op: FilterOp::Eq,
102            value: PredicateValue::Single(value),
103        }
104    }
105
106    /// Create a not-equal predicate
107    pub fn ne(column: impl Into<String>, value: Value) -> Self {
108        Self {
109            column: column.into(),
110            op: FilterOp::Ne,
111            value: PredicateValue::Single(value),
112        }
113    }
114
115    /// Create a less-than predicate
116    pub fn lt(column: impl Into<String>, value: Value) -> Self {
117        Self {
118            column: column.into(),
119            op: FilterOp::Lt,
120            value: PredicateValue::Single(value),
121        }
122    }
123
124    /// Create a less-than-or-equal predicate
125    pub fn le(column: impl Into<String>, value: Value) -> Self {
126        Self {
127            column: column.into(),
128            op: FilterOp::Le,
129            value: PredicateValue::Single(value),
130        }
131    }
132
133    /// Create a greater-than predicate
134    pub fn gt(column: impl Into<String>, value: Value) -> Self {
135        Self {
136            column: column.into(),
137            op: FilterOp::Gt,
138            value: PredicateValue::Single(value),
139        }
140    }
141
142    /// Create a greater-than-or-equal predicate
143    pub fn ge(column: impl Into<String>, value: Value) -> Self {
144        Self {
145            column: column.into(),
146            op: FilterOp::Ge,
147            value: PredicateValue::Single(value),
148        }
149    }
150
151    /// Create a between predicate (inclusive)
152    pub fn between(column: impl Into<String>, low: Value, high: Value) -> Self {
153        Self {
154            column: column.into(),
155            op: FilterOp::Between,
156            value: PredicateValue::Range(low, high),
157        }
158    }
159
160    /// Create an IN predicate
161    pub fn in_list(column: impl Into<String>, values: Vec<Value>) -> Self {
162        Self {
163            column: column.into(),
164            op: FilterOp::In,
165            value: PredicateValue::List(values),
166        }
167    }
168
169    /// Create a NOT IN predicate
170    pub fn not_in(column: impl Into<String>, values: Vec<Value>) -> Self {
171        Self {
172            column: column.into(),
173            op: FilterOp::NotIn,
174            value: PredicateValue::List(values),
175        }
176    }
177
178    /// Create a LIKE predicate
179    pub fn like(column: impl Into<String>, pattern: impl Into<String>) -> Self {
180        Self {
181            column: column.into(),
182            op: FilterOp::Like,
183            value: PredicateValue::Pattern(pattern.into()),
184        }
185    }
186
187    /// Create a NOT LIKE predicate
188    pub fn not_like(column: impl Into<String>, pattern: impl Into<String>) -> Self {
189        Self {
190            column: column.into(),
191            op: FilterOp::NotLike,
192            value: PredicateValue::Pattern(pattern.into()),
193        }
194    }
195
196    /// Create an IS NULL predicate
197    pub fn is_null(column: impl Into<String>) -> Self {
198        Self {
199            column: column.into(),
200            op: FilterOp::IsNull,
201            value: PredicateValue::None,
202        }
203    }
204
205    /// Create an IS NOT NULL predicate
206    pub fn is_not_null(column: impl Into<String>) -> Self {
207        Self {
208            column: column.into(),
209            op: FilterOp::IsNotNull,
210            value: PredicateValue::None,
211        }
212    }
213
214    /// Create a CONTAINS predicate
215    pub fn contains(column: impl Into<String>, value: Value) -> Self {
216        Self {
217            column: column.into(),
218            op: FilterOp::Contains,
219            value: PredicateValue::Single(value),
220        }
221    }
222
223    /// Create a STARTS WITH predicate
224    pub fn starts_with(column: impl Into<String>, prefix: impl Into<String>) -> Self {
225        Self {
226            column: column.into(),
227            op: FilterOp::StartsWith,
228            value: PredicateValue::Pattern(prefix.into()),
229        }
230    }
231
232    /// Create an ENDS WITH predicate
233    pub fn ends_with(column: impl Into<String>, suffix: impl Into<String>) -> Self {
234        Self {
235            column: column.into(),
236            op: FilterOp::EndsWith,
237            value: PredicateValue::Pattern(suffix.into()),
238        }
239    }
240
241    /// Evaluate predicate against a value
242    pub fn evaluate(&self, column_value: &Value) -> bool {
243        match (&self.op, &self.value) {
244            (FilterOp::Eq, PredicateValue::Single(v)) => column_value == v,
245            (FilterOp::Ne, PredicateValue::Single(v)) => column_value != v,
246            (FilterOp::Lt, PredicateValue::Single(v)) => {
247                partial_compare_values(column_value, v) == Some(std::cmp::Ordering::Less)
248            }
249            (FilterOp::Le, PredicateValue::Single(v)) => {
250                matches!(
251                    partial_compare_values(column_value, v),
252                    Some(std::cmp::Ordering::Less | std::cmp::Ordering::Equal)
253                )
254            }
255            (FilterOp::Gt, PredicateValue::Single(v)) => {
256                partial_compare_values(column_value, v) == Some(std::cmp::Ordering::Greater)
257            }
258            (FilterOp::Ge, PredicateValue::Single(v)) => {
259                matches!(
260                    partial_compare_values(column_value, v),
261                    Some(std::cmp::Ordering::Greater | std::cmp::Ordering::Equal)
262                )
263            }
264            (FilterOp::Between, PredicateValue::Range(low, high)) => {
265                matches!(
266                    partial_compare_values(column_value, low),
267                    Some(std::cmp::Ordering::Greater | std::cmp::Ordering::Equal)
268                ) && matches!(
269                    partial_compare_values(column_value, high),
270                    Some(std::cmp::Ordering::Less | std::cmp::Ordering::Equal)
271                )
272            }
273            (FilterOp::In, PredicateValue::List(values)) => values.contains(column_value),
274            (FilterOp::NotIn, PredicateValue::List(values)) => !values.contains(column_value),
275            (FilterOp::Like, PredicateValue::Pattern(pattern)) => match column_value {
276                Value::Text(s) => match_like_pattern(s, pattern),
277                _ => false,
278            },
279            (FilterOp::NotLike, PredicateValue::Pattern(pattern)) => match column_value {
280                Value::Text(s) => !match_like_pattern(s, pattern),
281                _ => true,
282            },
283            (FilterOp::IsNull, PredicateValue::None) => matches!(column_value, Value::Null),
284            (FilterOp::IsNotNull, PredicateValue::None) => !matches!(column_value, Value::Null),
285            (FilterOp::Contains, PredicateValue::Single(v)) => match (column_value, v) {
286                (Value::Text(haystack), Value::Text(needle)) => haystack.contains(needle.as_ref()),
287                _ => false,
288            },
289            (FilterOp::StartsWith, PredicateValue::Pattern(prefix)) => match column_value {
290                Value::Text(s) => s.starts_with(prefix),
291                _ => false,
292            },
293            (FilterOp::EndsWith, PredicateValue::Pattern(suffix)) => match column_value {
294                Value::Text(s) => s.ends_with(suffix),
295                _ => false,
296            },
297            _ => false,
298        }
299    }
300}
301
302/// Match SQL LIKE pattern
303///
304/// % matches any sequence of characters
305/// _ matches any single character
306fn match_like_pattern(text: &str, pattern: &str) -> bool {
307    let mut text_chars = text.chars().peekable();
308    let mut pattern_chars = pattern.chars().peekable();
309
310    match_like_recursive(&mut text_chars, &mut pattern_chars)
311}
312
313fn match_like_recursive<I: Iterator<Item = char> + Clone>(
314    text: &mut std::iter::Peekable<I>,
315    pattern: &mut std::iter::Peekable<I>,
316) -> bool {
317    loop {
318        match (pattern.peek().cloned(), text.peek().cloned()) {
319            // Pattern exhausted
320            (None, None) => return true,
321            (None, Some(_)) => return false,
322
323            // Wildcard %: match zero or more characters
324            (Some('%'), _) => {
325                pattern.next();
326                // Skip consecutive %
327                while pattern.peek() == Some(&'%') {
328                    pattern.next();
329                }
330                // If % is at end, match everything
331                if pattern.peek().is_none() {
332                    return true;
333                }
334                // Try matching from each position
335                loop {
336                    let mut text_clone = text.clone();
337                    let mut pattern_clone = pattern.clone();
338                    if match_like_recursive(&mut text_clone, &mut pattern_clone) {
339                        return true;
340                    }
341                    if text.next().is_none() {
342                        return false;
343                    }
344                }
345            }
346
347            // Single char wildcard _
348            (Some('_'), Some(_)) => {
349                pattern.next();
350                text.next();
351            }
352            (Some('_'), None) => return false,
353
354            // Literal match
355            (Some(p), Some(t)) => {
356                if p.to_lowercase().next() == t.to_lowercase().next() {
357                    pattern.next();
358                    text.next();
359                } else {
360                    return false;
361                }
362            }
363
364            // Pattern has more but text exhausted
365            (Some(_), None) => return false,
366        }
367    }
368}
369
370/// Composite filter with logical operations
371#[derive(Debug, Clone)]
372pub enum Filter {
373    /// Single predicate
374    Predicate(Predicate),
375    /// Logical AND of filters
376    And(Vec<Filter>),
377    /// Logical OR of filters
378    Or(Vec<Filter>),
379    /// Logical NOT of filter
380    Not(Box<Filter>),
381}
382
383impl Filter {
384    /// Create filter from predicate
385    pub fn from_predicate(predicate: Predicate) -> Self {
386        Filter::Predicate(predicate)
387    }
388
389    /// Create equality filter
390    pub fn eq(column: impl Into<String>, value: Value) -> Self {
391        Filter::Predicate(Predicate::eq(column, value))
392    }
393
394    /// Create not-equal filter
395    pub fn ne(column: impl Into<String>, value: Value) -> Self {
396        Filter::Predicate(Predicate::ne(column, value))
397    }
398
399    /// Create less-than filter
400    pub fn lt(column: impl Into<String>, value: Value) -> Self {
401        Filter::Predicate(Predicate::lt(column, value))
402    }
403
404    /// Create less-than-or-equal filter
405    pub fn le(column: impl Into<String>, value: Value) -> Self {
406        Filter::Predicate(Predicate::le(column, value))
407    }
408
409    /// Create greater-than filter
410    pub fn gt(column: impl Into<String>, value: Value) -> Self {
411        Filter::Predicate(Predicate::gt(column, value))
412    }
413
414    /// Create greater-than-or-equal filter
415    pub fn ge(column: impl Into<String>, value: Value) -> Self {
416        Filter::Predicate(Predicate::ge(column, value))
417    }
418
419    /// Create between filter
420    pub fn between(column: impl Into<String>, low: Value, high: Value) -> Self {
421        Filter::Predicate(Predicate::between(column, low, high))
422    }
423
424    /// Create IN filter
425    pub fn in_list(column: impl Into<String>, values: Vec<Value>) -> Self {
426        Filter::Predicate(Predicate::in_list(column, values))
427    }
428
429    /// Create LIKE filter
430    pub fn like(column: impl Into<String>, pattern: impl Into<String>) -> Self {
431        Filter::Predicate(Predicate::like(column, pattern))
432    }
433
434    /// Create IS NULL filter
435    pub fn is_null(column: impl Into<String>) -> Self {
436        Filter::Predicate(Predicate::is_null(column))
437    }
438
439    /// Create IS NOT NULL filter
440    pub fn is_not_null(column: impl Into<String>) -> Self {
441        Filter::Predicate(Predicate::is_not_null(column))
442    }
443
444    /// Create AND filter
445    pub fn and(filters: Vec<Filter>) -> Self {
446        Filter::And(filters)
447    }
448
449    /// Create OR filter
450    pub fn or(filters: Vec<Filter>) -> Self {
451        Filter::Or(filters)
452    }
453
454    /// Create NOT filter
455    pub fn not(filter: Filter) -> Self {
456        Filter::Not(Box::new(filter))
457    }
458
459    /// Combine with another filter using AND
460    pub fn and_filter(self, other: Filter) -> Self {
461        match self {
462            Filter::And(mut filters) => {
463                filters.push(other);
464                Filter::And(filters)
465            }
466            _ => Filter::And(vec![self, other]),
467        }
468    }
469
470    /// Combine with another filter using OR
471    pub fn or_filter(self, other: Filter) -> Self {
472        match self {
473            Filter::Or(mut filters) => {
474                filters.push(other);
475                Filter::Or(filters)
476            }
477            _ => Filter::Or(vec![self, other]),
478        }
479    }
480
481    /// Evaluate filter against a row (column name -> value map)
482    pub fn evaluate(&self, get_value: &impl Fn(&str) -> Option<Value>) -> bool {
483        match self {
484            Filter::Predicate(pred) => {
485                if let Some(value) = get_value(&pred.column) {
486                    pred.evaluate(&value)
487                } else {
488                    // Column not found - treat as NULL
489                    matches!(pred.op, FilterOp::IsNull)
490                }
491            }
492            Filter::And(filters) => filters.iter().all(|f| f.evaluate(get_value)),
493            Filter::Or(filters) => filters.iter().any(|f| f.evaluate(get_value)),
494            Filter::Not(filter) => !filter.evaluate(get_value),
495        }
496    }
497
498    /// Check if filter references a specific column
499    pub fn references_column(&self, column: &str) -> bool {
500        match self {
501            Filter::Predicate(pred) => pred.column == column,
502            Filter::And(filters) | Filter::Or(filters) => {
503                filters.iter().any(|f| f.references_column(column))
504            }
505            Filter::Not(filter) => filter.references_column(column),
506        }
507    }
508
509    /// Get all referenced columns
510    pub fn referenced_columns(&self) -> Vec<&str> {
511        let mut columns = Vec::new();
512        self.collect_columns(&mut columns);
513        columns
514    }
515
516    fn collect_columns<'a>(&'a self, columns: &mut Vec<&'a str>) {
517        match self {
518            Filter::Predicate(pred) => {
519                if !columns.contains(&pred.column.as_str()) {
520                    columns.push(&pred.column);
521                }
522            }
523            Filter::And(filters) | Filter::Or(filters) => {
524                for f in filters {
525                    f.collect_columns(columns);
526                }
527            }
528            Filter::Not(filter) => filter.collect_columns(columns),
529        }
530    }
531}
532
533#[cfg(test)]
534mod tests {
535    use super::*;
536
537    fn make_row() -> impl Fn(&str) -> Option<Value> {
538        |col: &str| -> Option<Value> {
539            match col {
540                "name" => Some(Value::text("Alice".to_string())),
541                "age" => Some(Value::Integer(25)),
542                "score" => Some(Value::Float(95.5)),
543                "active" => Some(Value::Boolean(true)),
544                "email" => Some(Value::text("alice@example.com".to_string())),
545                "nullable" => Some(Value::Null),
546                _ => None,
547            }
548        }
549    }
550
551    #[test]
552    fn test_predicate_eq() {
553        let pred = Predicate::eq("name", Value::text("Alice".to_string()));
554        assert!(pred.evaluate(&Value::text("Alice".to_string())));
555        assert!(!pred.evaluate(&Value::text("Bob".to_string())));
556    }
557
558    #[test]
559    fn test_predicate_ne() {
560        let pred = Predicate::ne("name", Value::text("Bob".to_string()));
561        assert!(pred.evaluate(&Value::text("Alice".to_string())));
562        assert!(!pred.evaluate(&Value::text("Bob".to_string())));
563    }
564
565    #[test]
566    fn test_predicate_lt() {
567        let pred = Predicate::lt("age", Value::Integer(30));
568        assert!(pred.evaluate(&Value::Integer(25)));
569        assert!(!pred.evaluate(&Value::Integer(30)));
570        assert!(!pred.evaluate(&Value::Integer(35)));
571    }
572
573    #[test]
574    fn test_predicate_le() {
575        let pred = Predicate::le("age", Value::Integer(30));
576        assert!(pred.evaluate(&Value::Integer(25)));
577        assert!(pred.evaluate(&Value::Integer(30)));
578        assert!(!pred.evaluate(&Value::Integer(35)));
579    }
580
581    #[test]
582    fn test_predicate_gt() {
583        let pred = Predicate::gt("age", Value::Integer(20));
584        assert!(pred.evaluate(&Value::Integer(25)));
585        assert!(!pred.evaluate(&Value::Integer(20)));
586        assert!(!pred.evaluate(&Value::Integer(15)));
587    }
588
589    #[test]
590    fn test_predicate_ge() {
591        let pred = Predicate::ge("age", Value::Integer(20));
592        assert!(pred.evaluate(&Value::Integer(25)));
593        assert!(pred.evaluate(&Value::Integer(20)));
594        assert!(!pred.evaluate(&Value::Integer(15)));
595    }
596
597    #[test]
598    fn test_predicate_between() {
599        let pred = Predicate::between("age", Value::Integer(20), Value::Integer(30));
600        assert!(pred.evaluate(&Value::Integer(25)));
601        assert!(pred.evaluate(&Value::Integer(20)));
602        assert!(pred.evaluate(&Value::Integer(30)));
603        assert!(!pred.evaluate(&Value::Integer(19)));
604        assert!(!pred.evaluate(&Value::Integer(31)));
605    }
606
607    #[test]
608    fn test_predicate_in() {
609        let pred = Predicate::in_list(
610            "name",
611            vec![
612                Value::text("Alice".to_string()),
613                Value::text("Bob".to_string()),
614            ],
615        );
616        assert!(pred.evaluate(&Value::text("Alice".to_string())));
617        assert!(pred.evaluate(&Value::text("Bob".to_string())));
618        assert!(!pred.evaluate(&Value::text("Charlie".to_string())));
619    }
620
621    #[test]
622    fn test_predicate_not_in() {
623        let pred = Predicate::not_in("name", vec![Value::text("Alice".to_string())]);
624        assert!(!pred.evaluate(&Value::text("Alice".to_string())));
625        assert!(pred.evaluate(&Value::text("Bob".to_string())));
626    }
627
628    #[test]
629    fn test_predicate_like() {
630        // % matches any sequence
631        let pred = Predicate::like("email", "%@example.com");
632        assert!(pred.evaluate(&Value::text("alice@example.com".to_string())));
633        assert!(pred.evaluate(&Value::text("bob@example.com".to_string())));
634        assert!(!pred.evaluate(&Value::text("alice@gmail.com".to_string())));
635
636        // _ matches single char
637        let pred2 = Predicate::like("name", "A_ice");
638        assert!(pred2.evaluate(&Value::text("Alice".to_string())));
639        assert!(!pred2.evaluate(&Value::text("Alicia".to_string())));
640
641        // Combined
642        let pred3 = Predicate::like("email", "a%@%.com");
643        assert!(pred3.evaluate(&Value::text("alice@example.com".to_string())));
644    }
645
646    #[test]
647    fn test_predicate_is_null() {
648        let pred = Predicate::is_null("nullable");
649        assert!(pred.evaluate(&Value::Null));
650        assert!(!pred.evaluate(&Value::Integer(0)));
651    }
652
653    #[test]
654    fn test_predicate_is_not_null() {
655        let pred = Predicate::is_not_null("name");
656        assert!(pred.evaluate(&Value::text("Alice".to_string())));
657        assert!(!pred.evaluate(&Value::Null));
658    }
659
660    #[test]
661    fn test_predicate_contains() {
662        let pred = Predicate::contains("email", Value::text("@example".to_string()));
663        assert!(pred.evaluate(&Value::text("alice@example.com".to_string())));
664        assert!(!pred.evaluate(&Value::text("alice@gmail.com".to_string())));
665    }
666
667    #[test]
668    fn test_predicate_starts_with() {
669        let pred = Predicate::starts_with("name", "Al");
670        assert!(pred.evaluate(&Value::text("Alice".to_string())));
671        assert!(pred.evaluate(&Value::text("Albert".to_string())));
672        assert!(!pred.evaluate(&Value::text("Bob".to_string())));
673    }
674
675    #[test]
676    fn test_predicate_ends_with() {
677        let pred = Predicate::ends_with("email", ".com");
678        assert!(pred.evaluate(&Value::text("alice@example.com".to_string())));
679        assert!(!pred.evaluate(&Value::text("alice@example.org".to_string())));
680    }
681
682    #[test]
683    fn test_filter_evaluate() {
684        let row = make_row();
685
686        // Simple equality
687        let filter = Filter::eq("name", Value::text("Alice".to_string()));
688        assert!(filter.evaluate(&row));
689
690        // Comparison
691        let filter = Filter::gt("age", Value::Integer(20));
692        assert!(filter.evaluate(&row));
693    }
694
695    #[test]
696    fn test_filter_and() {
697        let row = make_row();
698
699        let filter = Filter::and(vec![
700            Filter::eq("name", Value::text("Alice".to_string())),
701            Filter::gt("age", Value::Integer(20)),
702        ]);
703        assert!(filter.evaluate(&row));
704
705        let filter = Filter::and(vec![
706            Filter::eq("name", Value::text("Alice".to_string())),
707            Filter::gt("age", Value::Integer(30)), // Alice is 25, fails
708        ]);
709        assert!(!filter.evaluate(&row));
710    }
711
712    #[test]
713    fn test_filter_or() {
714        let row = make_row();
715
716        let filter = Filter::or(vec![
717            Filter::eq("name", Value::text("Bob".to_string())), // Fails
718            Filter::gt("age", Value::Integer(20)),              // Passes
719        ]);
720        assert!(filter.evaluate(&row));
721
722        let filter = Filter::or(vec![
723            Filter::eq("name", Value::text("Bob".to_string())),
724            Filter::lt("age", Value::Integer(20)),
725        ]);
726        assert!(!filter.evaluate(&row));
727    }
728
729    #[test]
730    fn test_filter_not() {
731        let row = make_row();
732
733        let filter = Filter::not(Filter::eq("name", Value::text("Bob".to_string())));
734        assert!(filter.evaluate(&row)); // Alice != Bob
735
736        let filter = Filter::not(Filter::eq("name", Value::text("Alice".to_string())));
737        assert!(!filter.evaluate(&row));
738    }
739
740    #[test]
741    fn test_filter_chain() {
742        let row = make_row();
743
744        let filter = Filter::eq("name", Value::text("Alice".to_string()))
745            .and_filter(Filter::gt("age", Value::Integer(20)))
746            .and_filter(Filter::lt("age", Value::Integer(30)));
747
748        assert!(filter.evaluate(&row));
749    }
750
751    #[test]
752    fn test_filter_referenced_columns() {
753        let filter = Filter::and(vec![
754            Filter::eq("name", Value::text("Alice".to_string())),
755            Filter::or(vec![
756                Filter::gt("age", Value::Integer(20)),
757                Filter::lt("score", Value::Float(100.0)),
758            ]),
759        ]);
760
761        let columns = filter.referenced_columns();
762        assert!(columns.contains(&"name"));
763        assert!(columns.contains(&"age"));
764        assert!(columns.contains(&"score"));
765        assert_eq!(columns.len(), 3);
766    }
767
768    #[test]
769    fn test_cross_type_comparison() {
770        // Integer vs Float
771        let pred = Predicate::lt("value", Value::Float(30.5));
772        assert!(pred.evaluate(&Value::Integer(30)));
773        assert!(!pred.evaluate(&Value::Integer(31)));
774    }
775
776    #[test]
777    fn test_like_edge_cases() {
778        // Empty pattern
779        let pred = Predicate::like("text", "");
780        assert!(pred.evaluate(&Value::text("".to_string())));
781        assert!(!pred.evaluate(&Value::text("a".to_string())));
782
783        // Only %
784        let pred = Predicate::like("text", "%");
785        assert!(pred.evaluate(&Value::text("anything".to_string())));
786        assert!(pred.evaluate(&Value::text("".to_string())));
787
788        // Multiple %
789        let pred = Predicate::like("text", "a%b%c");
790        assert!(pred.evaluate(&Value::text("abc".to_string())));
791        assert!(pred.evaluate(&Value::text("aXXbYYc".to_string())));
792        assert!(!pred.evaluate(&Value::text("ab".to_string())));
793    }
794}