Skip to main content

darwen/types/
predicate.rs

1use crate::{
2    error::Error,
3    prelude::{Scalar, Tuple},
4    types::AttributeName,
5};
6
7/// Describes a value used during predicate evaluation.
8///
9/// # Example
10///
11/// ```rust
12/// use darwen::prelude::{AttributeName, Expression, Scalar};
13///
14/// let expression = Expression::Attribute(AttributeName::from("age"));
15/// let constant = Expression::Const(Scalar::Integer(18));
16///
17/// assert!(matches!(expression, Expression::Attribute(_)));
18/// assert!(matches!(constant, Expression::Const(_)));
19/// ```
20#[derive(Debug)]
21pub enum Expression {
22    /// Reads a value from a tuple attribute.
23    Attribute(AttributeName),
24    /// Uses a constant scalar value.
25    Const(Scalar),
26}
27
28impl Expression {
29    /// Evaluates an expression against a tuple.
30    ///
31    /// # Example
32    ///
33    /// ```rust
34    /// use darwen::prelude::{AttributeName, Expression, Scalar, TupleBuilder};
35    ///
36    /// let tuple = TupleBuilder::new()
37    ///     .with_value(AttributeName::from("age"), Scalar::Integer(21))
38    ///     .build()?;
39    ///
40    /// assert_eq!(
41    ///     Expression::Attribute(AttributeName::from("age")).eval(&tuple)?,
42    ///     Scalar::Integer(21)
43    /// );
44    /// # Ok::<(), darwen::prelude::Error>(())
45    /// ```
46    ///
47    /// # Errors
48    ///
49    /// Returns [`Error::AttributeNotFound`] if the expression references an
50    /// attribute that does not exist in the tuple.
51    pub fn eval(&self, tuple: &Tuple) -> Result<Scalar, Error> {
52        match self {
53            Expression::Attribute(attr) => tuple
54                .get(attr)
55                .cloned()
56                .ok_or(Error::AttributeNotFound { name: attr.clone() }),
57            Expression::Const(val) => Ok(val.clone()),
58        }
59    }
60}
61
62impl From<AttributeName> for Expression {
63    fn from(attr: AttributeName) -> Self {
64        Expression::Attribute(attr)
65    }
66}
67
68impl From<Scalar> for Expression {
69    fn from(val: Scalar) -> Self {
70        Expression::Const(val)
71    }
72}
73
74/// Represents a boolean condition evaluated against a tuple.
75///
76/// # Example
77///
78/// ```rust
79/// use darwen::prelude::{AttributeName, Predicate, Scalar};
80///
81/// let predicate = Predicate::gt(AttributeName::from("age"), Scalar::Integer(18));
82///
83/// assert!(matches!(predicate, Predicate::Gt(_, _)));
84/// ```
85#[derive(Debug)]
86pub enum Predicate {
87    /// Logical negation of a predicate.
88    Not(Box<Predicate>),
89    /// Logical conjunction of two predicates.
90    ///
91    /// Both operands are always evaluated; this operator does not short-circuit.
92    And(Box<Predicate>, Box<Predicate>),
93    /// Logical disjunction of two predicates.
94    ///
95    /// Both operands are always evaluated; this operator does not short-circuit.
96    Or(Box<Predicate>, Box<Predicate>),
97    /// Equality comparison between two expressions.
98    ///
99    /// `=` is only valid for operands of the same scalar type:
100    /// `INTEGER = INTEGER`, `BOOLEAN = BOOLEAN`, `STRING = STRING`,
101    /// and `BINARY = BINARY`. Mixed-type comparisons return an error.
102    Eq(Expression, Expression),
103    /// Greater-than comparison between two expressions.
104    ///
105    /// `>` is only valid for `INTEGER > INTEGER`. All other comparisons return
106    /// an error.
107    Gt(Expression, Expression),
108    /// Less-than comparison between two expressions.
109    ///
110    /// `<` is only valid for `INTEGER < INTEGER`. All other comparisons return
111    /// an error.
112    Lt(Expression, Expression),
113}
114
115impl Predicate {
116    /// Creates a negated predicate.
117    ///
118    /// The nested predicate is always evaluated when the resulting predicate is
119    /// evaluated.
120    ///
121    /// # Example
122    ///
123    /// ```rust
124    /// use darwen::{tuple, prelude::{AttributeName, Predicate, Scalar}};
125    /// # use darwen::prelude::TupleBuilder;
126    ///
127    /// let tuple = tuple!(active = true)?;
128    /// let predicate = Predicate::not(Predicate::eq(
129    ///     AttributeName::from("active"),
130    ///     Scalar::Boolean(false),
131    /// ));
132    ///
133    /// assert!(predicate.eval(&tuple)?);
134    /// # Ok::<(), darwen::prelude::Error>(())
135    /// ```
136    pub fn not<P>(predicate: P) -> Self
137    where
138        P: Into<Predicate>,
139    {
140        Self::Not(Box::new(predicate.into()))
141    }
142
143    /// Creates a conjunction of two predicates.
144    ///
145    /// Both operands are always evaluated; this operator does not short-circuit.
146    ///
147    /// # Example
148    ///
149    /// ```rust
150    /// use darwen::{tuple, prelude::{AttributeName, Predicate, Scalar}};
151    /// # use darwen::prelude::TupleBuilder;
152    ///
153    /// let tuple = tuple!(age = 21, active = true)?;
154    /// let predicate = Predicate::and(
155    ///     Predicate::gt(AttributeName::from("age"), Scalar::Integer(18)),
156    ///     Predicate::eq(AttributeName::from("active"), Scalar::Boolean(true)),
157    /// );
158    ///
159    /// assert!(predicate.eval(&tuple)?);
160    /// # Ok::<(), darwen::prelude::Error>(())
161    /// ```
162    pub fn and<L, R>(lhs: L, rhs: R) -> Self
163    where
164        L: Into<Predicate>,
165        R: Into<Predicate>,
166    {
167        Self::And(Box::new(lhs.into()), Box::new(rhs.into()))
168    }
169
170    /// Creates a disjunction of two predicates.
171    ///
172    /// Both operands are always evaluated; this operator does not short-circuit.
173    ///
174    /// # Example
175    ///
176    /// ```rust
177    /// use darwen::{tuple, prelude::{AttributeName, Predicate, Scalar}};
178    /// # use darwen::prelude::TupleBuilder;
179    ///
180    /// let tuple = tuple!(city = "Berlin")?;
181    /// let predicate = Predicate::or(
182    ///     Predicate::eq(AttributeName::from("city"), Scalar::from("Berlin")),
183    ///     Predicate::eq(AttributeName::from("city"), Scalar::from("Paris")),
184    /// );
185    ///
186    /// assert!(predicate.eval(&tuple)?);
187    /// # Ok::<(), darwen::prelude::Error>(())
188    /// ```
189    pub fn or<L, R>(lhs: L, rhs: R) -> Self
190    where
191        L: Into<Predicate>,
192        R: Into<Predicate>,
193    {
194        Self::Or(Box::new(lhs.into()), Box::new(rhs.into()))
195    }
196
197    /// Creates an equality predicate from two expressions.
198    ///
199    /// `=` is only valid for operands of the same scalar type:
200    /// `INTEGER = INTEGER`, `BOOLEAN = BOOLEAN`, `STRING = STRING`,
201    /// and `BINARY = BINARY`. Mixed-type comparisons return an error.
202    ///
203    /// # Example
204    ///
205    /// ```rust
206    /// use darwen::{tuple, prelude::{AttributeName, Predicate, Scalar}};
207    /// # use darwen::prelude::TupleBuilder;
208    ///
209    /// let tuple = tuple!(name = "Monica")?;
210    /// let predicate = Predicate::eq(AttributeName::from("name"), Scalar::from("Monica"));
211    ///
212    /// assert!(predicate.eval(&tuple)?);
213    /// # Ok::<(), darwen::prelude::Error>(())
214    /// ```
215    ///
216    /// Passing an owned `String` directly is intentionally rejected to avoid
217    /// ambiguity between attribute names and string constants.
218    ///
219    /// ```compile_fail
220    /// use darwen::prelude::{AttributeName, Predicate};
221    ///
222    /// let city_name = String::from("Berlin");
223    /// let _predicate = Predicate::eq(AttributeName::from("city"), city_name);
224    /// ```
225    pub fn eq<L, R>(lhs: L, rhs: R) -> Self
226    where
227        L: Into<Expression>,
228        R: Into<Expression>,
229    {
230        Predicate::Eq(lhs.into(), rhs.into())
231    }
232
233    /// Creates a greater-than predicate from two expressions.
234    ///
235    /// `>` is only valid for `INTEGER > INTEGER`. All other comparisons return
236    /// an error.
237    ///
238    /// # Example
239    ///
240    /// ```rust
241    /// use darwen::{tuple, prelude::{AttributeName, Predicate, Scalar}};
242    /// # use darwen::prelude::TupleBuilder;
243    ///
244    /// let tuple = tuple!(age = 21)?;
245    /// let predicate = Predicate::gt(AttributeName::from("age"), Scalar::from(20_i64));
246    ///
247    /// assert!(predicate.eval(&tuple)?);
248    /// # Ok::<(), darwen::prelude::Error>(())
249    /// ```
250    pub fn gt<L, R>(lhs: L, rhs: R) -> Self
251    where
252        L: Into<Expression>,
253        R: Into<Expression>,
254    {
255        Predicate::Gt(lhs.into(), rhs.into())
256    }
257
258    /// Creates a less-than predicate from two expressions.
259    ///
260    /// `<` is only valid for `INTEGER < INTEGER`. All other comparisons return
261    /// an error.
262    ///
263    /// # Example
264    ///
265    /// ```rust
266    /// use darwen::{tuple, prelude::{AttributeName, Predicate, Scalar}};
267    /// # use darwen::prelude::TupleBuilder;
268    ///
269    /// let tuple = tuple!(age = 21)?;
270    /// let predicate = Predicate::lt(AttributeName::from("age"), Scalar::from(30_i64));
271    ///
272    /// assert!(predicate.eval(&tuple)?);
273    /// # Ok::<(), darwen::prelude::Error>(())
274    /// ```
275    pub fn lt<L, R>(lhs: L, rhs: R) -> Self
276    where
277        L: Into<Expression>,
278        R: Into<Expression>,
279    {
280        Predicate::Lt(lhs.into(), rhs.into())
281    }
282
283    /// Evaluates a predicate against a tuple.
284    ///
285    /// # Example
286    ///
287    /// ```rust
288    /// use darwen::prelude::{AttributeName, Predicate, Scalar, TupleBuilder};
289    ///
290    /// let tuple = TupleBuilder::new()
291    ///     .with_value(AttributeName::from("age"), Scalar::Integer(21))
292    ///     .build()?;
293    ///
294    /// let predicate = Predicate::gt(AttributeName::from("age"), Scalar::Integer(18));
295    ///
296    /// assert!(predicate.eval(&tuple)?);
297    /// # Ok::<(), darwen::prelude::Error>(())
298    /// ```
299    ///
300    /// # Errors
301    ///
302    /// Returns [`Error::AttributeNotFound`] if one of the predicate expressions
303    /// references an attribute that does not exist in the tuple.
304    /// Returns [`Error::ScalarTypeMismatch`] if `Eq` compares operands of
305    /// different scalar types.
306    /// Returns [`Error::NonComparableTypes`] if `Gt` or `Lt` is used with
307    /// non-integer operands.
308    pub fn eval(&self, tuple: &Tuple) -> Result<bool, Error> {
309        match self {
310            Predicate::Not(expr) => {
311                let expr = expr.eval(tuple)?;
312                Ok(!expr)
313            }
314            Predicate::And(lhs, rhs) => {
315                let lhs = lhs.eval(tuple)?;
316                let rhs = rhs.eval(tuple)?;
317                Ok(lhs && rhs)
318            }
319            Predicate::Or(lhs, rhs) => {
320                let lhs = lhs.eval(tuple)?;
321                let rhs = rhs.eval(tuple)?;
322                Ok(lhs || rhs)
323            }
324            Predicate::Eq(lhs, rhs) => {
325                let lhs = lhs.eval(tuple)?;
326                let rhs = rhs.eval(tuple)?;
327                if lhs.ty() != rhs.ty() {
328                    return Err(Error::ScalarTypeMismatch {
329                        lhs: lhs.ty(),
330                        rhs: rhs.ty(),
331                    });
332                }
333                Ok(lhs == rhs)
334            }
335            Predicate::Gt(lhs, rhs) => {
336                let lhs = lhs.eval(tuple)?;
337                let rhs = rhs.eval(tuple)?;
338                if !matches!(lhs, Scalar::Integer(_)) || !matches!(rhs, Scalar::Integer(_)) {
339                    return Err(Error::NonComparableTypes {
340                        lhs: lhs.ty(),
341                        rhs: rhs.ty(),
342                    });
343                }
344                Ok(lhs > rhs)
345            }
346            Predicate::Lt(lhs, rhs) => {
347                let lhs = lhs.eval(tuple)?;
348                let rhs = rhs.eval(tuple)?;
349                if !matches!(lhs, Scalar::Integer(_)) || !matches!(rhs, Scalar::Integer(_)) {
350                    return Err(Error::NonComparableTypes {
351                        lhs: lhs.ty(),
352                        rhs: rhs.ty(),
353                    });
354                }
355                Ok(lhs < rhs)
356            }
357        }
358    }
359}
360
361#[cfg(test)]
362mod tests {
363    use super::*;
364
365    fn integer_tuple(name: &str, value: i64) -> Tuple {
366        Tuple::try_from(vec![(AttributeName::from(name), Scalar::Integer(value))]).unwrap()
367    }
368
369    fn boolean_tuple(name: &str, value: bool) -> Tuple {
370        Tuple::try_from(vec![(AttributeName::from(name), Scalar::Boolean(value))]).unwrap()
371    }
372
373    fn binary_tuple(name: &str, value: Vec<u8>) -> Tuple {
374        Tuple::try_from(vec![(AttributeName::from(name), Scalar::Binary(value))]).unwrap()
375    }
376
377    fn string_tuple(name: &str, value: &str) -> Tuple {
378        Tuple::try_from(vec![(
379            AttributeName::from(name),
380            Scalar::String(value.to_string()),
381        )])
382        .unwrap()
383    }
384
385    #[test]
386    fn test_expression_from_attribute_name() {
387        let expression = Expression::from(AttributeName::from("age"));
388
389        match expression {
390            Expression::Attribute(attribute) => assert_eq!(attribute, AttributeName::from("age")),
391            Expression::Const(_) => panic!("expected attribute expression"),
392        }
393    }
394
395    #[test]
396    fn test_expression_from_scalar() {
397        let expression = Expression::from(Scalar::Integer(42));
398
399        match expression {
400            Expression::Const(value) => assert_eq!(value, Scalar::Integer(42)),
401            Expression::Attribute(_) => panic!("expected const expression"),
402        }
403    }
404
405    #[test]
406    fn test_and() {
407        let tuple = integer_tuple("foo", 42);
408        let predicate = Predicate::and(
409            Predicate::eq(AttributeName::from("foo"), Scalar::Integer(42)),
410            Predicate::eq(AttributeName::from("foo"), Scalar::Integer(42)),
411        );
412        assert!(predicate.eval(&tuple).unwrap());
413    }
414
415    #[test]
416    fn test_predicate_eq_helper() {
417        let predicate = Predicate::eq(AttributeName::from("age"), Scalar::Integer(42));
418
419        match predicate {
420            Predicate::Eq(Expression::Attribute(attribute), Expression::Const(value)) => {
421                assert_eq!(attribute, AttributeName::from("age"));
422                assert_eq!(value, Scalar::Integer(42));
423            }
424            _ => panic!("expected eq predicate"),
425        }
426    }
427
428    #[test]
429    fn test_eq() {
430        let tuple = integer_tuple("foo", 42);
431        let predicate = Predicate::eq(AttributeName::from("foo"), Scalar::Integer(42));
432        assert!(predicate.eval(&tuple).unwrap());
433    }
434
435    #[test]
436    fn test_eq_supports_boolean_operands() {
437        let tuple = boolean_tuple("active", true);
438        let predicate = Predicate::eq(AttributeName::from("active"), Scalar::Boolean(true));
439
440        assert!(predicate.eval(&tuple).unwrap());
441    }
442
443    #[test]
444    fn test_eq_supports_string_operands() {
445        let tuple = string_tuple("city", "Berlin");
446        let predicate = Predicate::eq(AttributeName::from("city"), Scalar::String("Berlin".into()));
447
448        assert!(predicate.eval(&tuple).unwrap());
449    }
450
451    #[test]
452    fn test_eq_supports_binary_operands() {
453        let tuple = binary_tuple("payload", vec![1, 2, 3]);
454        let predicate = Predicate::eq(
455            AttributeName::from("payload"),
456            Scalar::Binary(vec![1, 2, 3]),
457        );
458
459        assert!(predicate.eval(&tuple).unwrap());
460    }
461
462    #[test]
463    fn test_predicate_gt_helper() {
464        let predicate = Predicate::gt(AttributeName::from("age"), Scalar::Integer(18));
465
466        match predicate {
467            Predicate::Gt(Expression::Attribute(attribute), Expression::Const(value)) => {
468                assert_eq!(attribute, AttributeName::from("age"));
469                assert_eq!(value, Scalar::Integer(18));
470            }
471            _ => panic!("expected gt predicate"),
472        }
473    }
474
475    #[test]
476    fn test_not() {
477        let tuple = integer_tuple("foo", 42);
478        let predicate = Predicate::not(Predicate::eq(
479            AttributeName::from("foo"),
480            Scalar::Integer(42),
481        ));
482
483        assert!(!predicate.eval(&tuple).unwrap());
484    }
485
486    #[test]
487    fn test_predicate_lt_helper() {
488        let predicate = Predicate::lt(AttributeName::from("age"), Scalar::Integer(30));
489
490        match predicate {
491            Predicate::Lt(Expression::Attribute(attribute), Expression::Const(value)) => {
492                assert_eq!(attribute, AttributeName::from("age"));
493                assert_eq!(value, Scalar::Integer(30));
494            }
495            _ => panic!("expected lt predicate"),
496        }
497    }
498
499    #[test]
500    fn test_or() {
501        let tuple = integer_tuple("foo", 42);
502        let predicate = Predicate::or(
503            Predicate::eq(AttributeName::from("foo"), Scalar::Integer(0)),
504            Predicate::eq(AttributeName::from("foo"), Scalar::Integer(42)),
505        );
506
507        assert!(predicate.eval(&tuple).unwrap());
508    }
509
510    #[test]
511    fn test_expression_const_returns_value() -> Result<(), Error> {
512        let tuple = Tuple::empty();
513
514        assert_eq!(
515            Expression::Const(Scalar::Boolean(true)).eval(&tuple)?,
516            Scalar::Boolean(true)
517        );
518        Ok(())
519    }
520
521    #[test]
522    fn test_expression_attribute_missing_is_error() {
523        let tuple = Tuple::empty();
524
525        assert_eq!(
526            Expression::Attribute(AttributeName::from("missing")).eval(&tuple),
527            Err(Error::AttributeNotFound {
528                name: AttributeName::from("missing")
529            })
530        );
531    }
532
533    #[test]
534    fn test_and_propagates_attribute_errors() {
535        let tuple = integer_tuple("foo", 42);
536        let predicate = Predicate::and(
537            Predicate::eq(AttributeName::from("foo"), Scalar::Integer(42)),
538            Predicate::eq(AttributeName::from("missing"), Scalar::Integer(42)),
539        );
540
541        assert_eq!(
542            predicate.eval(&tuple),
543            Err(Error::AttributeNotFound {
544                name: AttributeName::from("missing")
545            })
546        );
547    }
548
549    #[test]
550    fn test_and_does_not_hide_missing_attribute_when_lhs_is_false() {
551        let tuple = integer_tuple("foo", 42);
552        let predicate = Predicate::and(
553            Predicate::eq(AttributeName::from("foo"), Scalar::Integer(0)),
554            Predicate::eq(AttributeName::from("missing"), Scalar::Integer(42)),
555        );
556
557        assert_eq!(
558            predicate.eval(&tuple),
559            Err(Error::AttributeNotFound {
560                name: AttributeName::from("missing")
561            })
562        );
563    }
564
565    #[test]
566    fn test_or_propagates_attribute_errors() {
567        let tuple = integer_tuple("foo", 42);
568        let predicate = Predicate::or(
569            Predicate::eq(AttributeName::from("foo"), Scalar::Integer(42)),
570            Predicate::eq(AttributeName::from("missing"), Scalar::Integer(42)),
571        );
572
573        assert_eq!(
574            predicate.eval(&tuple),
575            Err(Error::AttributeNotFound {
576                name: AttributeName::from("missing")
577            })
578        );
579    }
580
581    #[test]
582    fn test_gt() {
583        let tuple = integer_tuple("age", 42);
584        let predicate = Predicate::gt(AttributeName::from("age"), Scalar::Integer(20));
585
586        assert!(predicate.eval(&tuple).unwrap());
587    }
588
589    #[test]
590    fn test_gt_rejects_heterogeneous_scalar_types() {
591        let tuple = integer_tuple("age", 42);
592        let predicate = Predicate::gt(AttributeName::from("age"), Scalar::String("20".into()));
593
594        assert!(
595            predicate.eval(&tuple).is_err(),
596            "greater-than comparison between different scalar types must return an error"
597        );
598    }
599
600    #[test]
601    fn test_gt_rejects_string_comparisons() {
602        let tuple = string_tuple("city", "Berlin");
603        let predicate = Predicate::gt(
604            AttributeName::from("city"),
605            Scalar::String("Amsterdam".into()),
606        );
607
608        assert!(
609            predicate.eval(&tuple).is_err(),
610            "greater-than comparison for string operands must return an error"
611        );
612    }
613
614    #[test]
615    fn test_gt_rejects_boolean_comparisons() {
616        let tuple = boolean_tuple("active", true);
617        let predicate = Predicate::gt(AttributeName::from("active"), Scalar::Boolean(false));
618
619        assert!(
620            predicate.eval(&tuple).is_err(),
621            "greater-than comparison for boolean operands must return an error"
622        );
623    }
624
625    #[test]
626    fn test_gt_rejects_binary_comparisons() {
627        let tuple = binary_tuple("payload", vec![1, 2, 3]);
628        let predicate = Predicate::gt(
629            AttributeName::from("payload"),
630            Scalar::Binary(vec![0, 1, 2]),
631        );
632
633        assert!(
634            predicate.eval(&tuple).is_err(),
635            "greater-than comparison for binary operands must return an error"
636        );
637    }
638
639    #[test]
640    fn test_lt() {
641        let tuple = integer_tuple("age", 18);
642        let predicate = Predicate::lt(AttributeName::from("age"), Scalar::Integer(20));
643
644        assert!(predicate.eval(&tuple).unwrap());
645    }
646
647    #[test]
648    fn test_eq_rejects_heterogeneous_scalar_types() {
649        let tuple = integer_tuple("age", 42);
650        let predicate = Predicate::eq(AttributeName::from("age"), Scalar::String("42".into()));
651
652        assert!(
653            predicate.eval(&tuple).is_err(),
654            "equality comparison between different scalar types must return an error"
655        );
656    }
657
658    #[test]
659    fn test_lt_rejects_heterogeneous_scalar_types() {
660        let tuple = integer_tuple("age", 42);
661        let predicate = Predicate::lt(AttributeName::from("age"), Scalar::String("20".into()));
662
663        assert!(
664            predicate.eval(&tuple).is_err(),
665            "less-than comparison between different scalar types must return an error"
666        );
667    }
668
669    #[test]
670    fn test_lt_rejects_string_comparisons() {
671        let tuple = string_tuple("city", "Berlin");
672        let predicate = Predicate::lt(AttributeName::from("city"), Scalar::String("Paris".into()));
673
674        assert!(
675            predicate.eval(&tuple).is_err(),
676            "less-than comparison for string operands must return an error"
677        );
678    }
679
680    #[test]
681    fn test_lt_rejects_boolean_comparisons() {
682        let tuple = boolean_tuple("active", true);
683        let predicate = Predicate::lt(AttributeName::from("active"), Scalar::Boolean(false));
684
685        assert!(
686            predicate.eval(&tuple).is_err(),
687            "less-than comparison for boolean operands must return an error"
688        );
689    }
690
691    #[test]
692    fn test_lt_rejects_binary_comparisons() {
693        let tuple = binary_tuple("payload", vec![1, 2, 3]);
694        let predicate = Predicate::lt(
695            AttributeName::from("payload"),
696            Scalar::Binary(vec![4, 5, 6]),
697        );
698
699        assert!(
700            predicate.eval(&tuple).is_err(),
701            "less-than comparison for binary operands must return an error"
702        );
703    }
704}