icydb_core/db/query/predicate/
eval.rs1use super::{
2 ast::{CompareOp, ComparePredicate, Predicate},
3 coercion::{CoercionSpec, TextOp, compare_eq, compare_order, compare_text},
4};
5use crate::{traits::FieldValues, value::Value};
6use std::cmp::Ordering;
7
8#[derive(Clone, Debug, Eq, PartialEq)]
16pub enum FieldPresence {
17 Present(Value),
19 Missing,
21}
22
23pub trait Row {
30 fn field(&self, name: &str) -> FieldPresence;
31}
32
33impl<T: FieldValues> Row for T {
38 fn field(&self, name: &str) -> FieldPresence {
39 match self.get_value(name) {
40 Some(value) => FieldPresence::Present(value),
41 None => FieldPresence::Missing,
42 }
43 }
44}
45
46#[must_use]
57#[expect(clippy::match_like_matches_macro)]
58pub fn eval<R: Row + ?Sized>(row: &R, predicate: &Predicate) -> bool {
59 match predicate {
60 Predicate::True => true,
61 Predicate::False => false,
62
63 Predicate::And(children) => children.iter().all(|child| eval(row, child)),
64 Predicate::Or(children) => children.iter().any(|child| eval(row, child)),
65 Predicate::Not(inner) => !eval(row, inner),
66
67 Predicate::Compare(cmp) => eval_compare(row, cmp),
68
69 Predicate::IsNull { field } => match row.field(field) {
70 FieldPresence::Present(Value::None) => true,
71 _ => false,
72 },
73
74 Predicate::IsMissing { field } => matches!(row.field(field), FieldPresence::Missing),
75
76 Predicate::IsEmpty { field } => match row.field(field) {
77 FieldPresence::Present(value) => is_empty_value(&value),
78 FieldPresence::Missing => false,
79 },
80
81 Predicate::IsNotEmpty { field } => match row.field(field) {
82 FieldPresence::Present(value) => !is_empty_value(&value),
83 FieldPresence::Missing => false,
84 },
85
86 Predicate::MapContainsKey {
87 field,
88 key,
89 coercion,
90 } => match row.field(field) {
91 FieldPresence::Present(value) => map_contains_key(&value, key, coercion),
92 FieldPresence::Missing => false,
93 },
94
95 Predicate::MapContainsValue {
96 field,
97 value,
98 coercion,
99 } => match row.field(field) {
100 FieldPresence::Present(actual) => map_contains_value(&actual, value, coercion),
101 FieldPresence::Missing => false,
102 },
103
104 Predicate::MapContainsEntry {
105 field,
106 key,
107 value,
108 coercion,
109 } => match row.field(field) {
110 FieldPresence::Present(actual) => map_contains_entry(&actual, key, value, coercion),
111 FieldPresence::Missing => false,
112 },
113 }
114}
115
116fn eval_compare<R: Row + ?Sized>(row: &R, cmp: &ComparePredicate) -> bool {
124 let ComparePredicate {
125 field,
126 op,
127 value,
128 coercion,
129 } = cmp;
130
131 let FieldPresence::Present(actual) = row.field(field) else {
132 return false;
133 };
134
135 match op {
136 CompareOp::Eq => compare_eq(&actual, value, coercion).unwrap_or(false),
137 CompareOp::Ne => compare_eq(&actual, value, coercion).is_some_and(|v| !v),
138
139 CompareOp::Lt => compare_order(&actual, value, coercion).is_some_and(Ordering::is_lt),
140 CompareOp::Lte => compare_order(&actual, value, coercion).is_some_and(Ordering::is_le),
141 CompareOp::Gt => compare_order(&actual, value, coercion).is_some_and(Ordering::is_gt),
142 CompareOp::Gte => compare_order(&actual, value, coercion).is_some_and(Ordering::is_ge),
143
144 CompareOp::In => in_list(&actual, value, coercion),
145 CompareOp::NotIn => !in_list(&actual, value, coercion),
146
147 CompareOp::AnyIn => any_in(&actual, value, coercion),
148 CompareOp::AllIn => all_in(&actual, value, coercion),
149
150 CompareOp::Contains => contains(&actual, value, coercion),
151
152 CompareOp::StartsWith => {
153 compare_text(&actual, value, coercion, TextOp::StartsWith).unwrap_or(false)
154 }
155 CompareOp::EndsWith => {
156 compare_text(&actual, value, coercion, TextOp::EndsWith).unwrap_or(false)
157 }
158 }
159}
160
161const fn is_empty_value(value: &Value) -> bool {
165 match value {
166 Value::Text(text) => text.is_empty(),
167 Value::List(items) => items.is_empty(),
168 _ => false,
169 }
170}
171
172fn in_list(actual: &Value, list: &Value, coercion: &CoercionSpec) -> bool {
176 let Value::List(items) = list else {
177 return false;
178 };
179
180 items
181 .iter()
182 .any(|item| compare_eq(actual, item, coercion).unwrap_or(false))
183}
184
185fn any_in(actual: &Value, list: &Value, coercion: &CoercionSpec) -> bool {
189 let Value::List(actual_items) = actual else {
190 return false;
191 };
192 let Value::List(needles) = list else {
193 return false;
194 };
195
196 actual_items.iter().any(|item| {
197 needles
198 .iter()
199 .any(|needle| compare_eq(item, needle, coercion).unwrap_or(false))
200 })
201}
202
203fn all_in(actual: &Value, list: &Value, coercion: &CoercionSpec) -> bool {
207 let Value::List(actual_items) = actual else {
208 return false;
209 };
210 let Value::List(needles) = list else {
211 return false;
212 };
213
214 actual_items.iter().all(|item| {
215 needles
216 .iter()
217 .any(|needle| compare_eq(item, needle, coercion).unwrap_or(false))
218 })
219}
220
221fn contains(actual: &Value, needle: &Value, coercion: &CoercionSpec) -> bool {
228 if let Some(res) = compare_text(actual, needle, coercion, TextOp::Contains) {
229 return res;
230 }
231
232 let Value::List(items) = actual else {
233 return false;
234 };
235
236 items
237 .iter()
238 .any(|item| compare_eq(item, needle, coercion).unwrap_or(false))
239}
240
241fn map_contains_key(map: &Value, key: &Value, coercion: &CoercionSpec) -> bool {
247 let Value::List(entries) = map else {
248 return false;
249 };
250
251 for entry in entries {
252 let Value::List(pair) = entry else {
253 return false;
254 };
255 if pair.len() != 2 {
256 return false;
257 }
258 if compare_eq(&pair[0], key, coercion).unwrap_or(false) {
259 return true;
260 }
261 }
262
263 false
264}
265
266fn map_contains_value(map: &Value, value: &Value, coercion: &CoercionSpec) -> bool {
270 let Value::List(entries) = map else {
271 return false;
272 };
273
274 for entry in entries {
275 let Value::List(pair) = entry else {
276 return false;
277 };
278 if pair.len() != 2 {
279 return false;
280 }
281 if compare_eq(&pair[1], value, coercion).unwrap_or(false) {
282 return true;
283 }
284 }
285
286 false
287}
288
289fn map_contains_entry(map: &Value, key: &Value, value: &Value, coercion: &CoercionSpec) -> bool {
293 let Value::List(entries) = map else {
294 return false;
295 };
296
297 for entry in entries {
298 let Value::List(pair) = entry else {
299 return false;
300 };
301 if pair.len() != 2 {
302 return false;
303 }
304
305 let key_match = compare_eq(&pair[0], key, coercion).unwrap_or(false);
306 let value_match = compare_eq(&pair[1], value, coercion).unwrap_or(false);
307
308 if key_match && value_match {
309 return true;
310 }
311 }
312
313 false
314}