1use std::collections::BTreeSet;
16
17use crate::types::Value;
18
19#[derive(Debug, Clone, PartialEq)]
20pub enum Predicate {
21 Equals(Value),
22 NotEquals(Value),
23 GreaterThan(Value),
24 GreaterThanOrEqual(Value),
25 LessThan(Value),
26 LessThanOrEqual(Value),
27 InSet(BTreeSet<Value>),
28 Between { low: Value, high: Value },
29 IsNull,
30 IsNotNull,
31}
32
33impl Predicate {
34 pub fn is_null_aware(&self) -> bool {
38 matches!(self, Predicate::IsNull | Predicate::IsNotNull)
39 }
40
41 pub fn evaluate(&self, value: Option<&Value>) -> bool {
45 let is_null = matches!(value, None | Some(Value::Null));
46 match self {
47 Predicate::IsNull => is_null,
48 Predicate::IsNotNull => !is_null,
49 Predicate::Equals(target) => !is_null && value.is_some_and(|v| values_equal(v, target)),
50 Predicate::NotEquals(target) => {
51 !is_null && value.is_some_and(|v| !values_equal(v, target))
52 }
53 Predicate::GreaterThan(target) => {
54 !is_null && value.is_some_and(|v| compare_values(v, target).is_gt())
55 }
56 Predicate::GreaterThanOrEqual(target) => {
57 !is_null && value.is_some_and(|v| compare_values(v, target).is_ge())
58 }
59 Predicate::LessThan(target) => {
60 !is_null && value.is_some_and(|v| compare_values(v, target).is_lt())
61 }
62 Predicate::LessThanOrEqual(target) => {
63 !is_null && value.is_some_and(|v| compare_values(v, target).is_le())
64 }
65 Predicate::InSet(values) => {
66 !is_null
67 && value.is_some_and(|v| values.iter().any(|target| values_equal(v, target)))
68 }
69 Predicate::Between { low, high } => {
70 !is_null
71 && value.is_some_and(|v| {
72 compare_values(v, low).is_ge() && compare_values(v, high).is_le()
73 })
74 }
75 }
76 }
77}
78
79fn values_equal(a: &Value, b: &Value) -> bool {
80 match (a, b) {
81 (Value::Temporal(x), Value::Temporal(y)) => x == y,
82 (Value::Temporal(x), Value::Str(y)) | (Value::Str(y), Value::Temporal(x)) => {
83 x.parse_same_kind(y).is_some_and(|parsed| parsed == *x)
84 }
85 (Value::FixedChar(x) | Value::Str(x), Value::FixedChar(y))
86 | (Value::FixedChar(x), Value::Str(y)) => {
87 x.trim_end_matches(' ') == y.trim_end_matches(' ')
88 }
89 _ => a == b,
90 }
91}
92
93fn compare_values(a: &Value, b: &Value) -> std::cmp::Ordering {
94 match (a, b) {
95 (Value::Temporal(x), Value::Temporal(y)) => x.cmp(y),
96 (Value::Temporal(x), Value::Str(y)) => x
97 .parse_same_kind(y)
98 .map_or_else(|| a.cmp(b), |parsed| x.cmp(&parsed)),
99 (Value::Str(x), Value::Temporal(y)) => y
100 .parse_same_kind(x)
101 .map_or_else(|| a.cmp(b), |parsed| parsed.cmp(y)),
102 (Value::FixedChar(x) | Value::Str(x), Value::FixedChar(y))
103 | (Value::FixedChar(x), Value::Str(y)) => {
104 x.trim_end_matches(' ').cmp(y.trim_end_matches(' '))
105 }
106 _ => a.cmp(b),
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 fn iv(n: i64) -> Value {
115 Value::Int(n)
116 }
117
118 #[test]
119 fn equals_matches_exact() {
120 let p = Predicate::Equals(iv(42));
121 assert!(p.evaluate(Some(&iv(42))));
122 assert!(!p.evaluate(Some(&iv(43))));
123 assert!(!p.evaluate(None));
124 }
125
126 #[test]
127 fn comparators_respect_ordering() {
128 let p = Predicate::GreaterThan(iv(10));
129 assert!(p.evaluate(Some(&iv(11))));
130 assert!(!p.evaluate(Some(&iv(10))));
131 assert!(!p.evaluate(Some(&iv(9))));
132 }
133
134 #[test]
135 fn between_inclusive_bounds() {
136 let p = Predicate::Between {
137 low: iv(1),
138 high: iv(3),
139 };
140 assert!(p.evaluate(Some(&iv(1))));
141 assert!(p.evaluate(Some(&iv(3))));
142 assert!(!p.evaluate(Some(&iv(0))));
143 assert!(!p.evaluate(Some(&iv(4))));
144 }
145
146 #[test]
147 fn in_set_membership() {
148 let s: BTreeSet<Value> = [iv(1), iv(2), iv(5)].into_iter().collect();
149 let p = Predicate::InSet(s);
150 assert!(p.evaluate(Some(&iv(2))));
151 assert!(!p.evaluate(Some(&iv(3))));
152 }
153
154 #[test]
155 fn null_aware_predicates_see_none() {
156 assert!(Predicate::IsNull.evaluate(None));
157 assert!(!Predicate::IsNull.evaluate(Some(&iv(0))));
158 assert!(!Predicate::IsNotNull.evaluate(None));
159 assert!(Predicate::IsNotNull.evaluate(Some(&iv(0))));
160 }
161
162 #[test]
163 fn fixed_character_predicates_ignore_blank_padding() {
164 let fixed = Value::FixedChar("x ".into());
165 assert!(Predicate::Equals(Value::Str("x".into())).evaluate(Some(&fixed)));
166 assert!(Predicate::Equals(Value::Str("x ".into())).evaluate(Some(&fixed)));
167 assert!(Predicate::LessThan(Value::Str("y".into())).evaluate(Some(&fixed)));
168 }
169}