1use crate::{
7 db::predicate::coercion::{CoercionId, CoercionSpec},
8 value::Value,
9};
10use std::ops::{BitAnd, BitOr};
11
12#[cfg_attr(doc, doc = "Predicate")]
13#[derive(Clone, Debug, Eq, PartialEq)]
14pub enum Predicate {
15 True,
16 False,
17 And(Vec<Self>),
18 Or(Vec<Self>),
19 Not(Box<Self>),
20 Compare(ComparePredicate),
21 CompareFields(CompareFieldsPredicate),
22 IsNull { field: String },
23 IsNotNull { field: String },
24 IsMissing { field: String },
25 IsEmpty { field: String },
26 IsNotEmpty { field: String },
27 TextContains { field: String, value: Value },
28 TextContainsCi { field: String, value: Value },
29}
30
31impl Predicate {
32 pub(in crate::db) fn try_for_each_field<E>(
37 &self,
38 visit: &mut impl FnMut(&str) -> Result<(), E>,
39 ) -> Result<(), E> {
40 match self {
41 Self::True | Self::False => Ok(()),
42 Self::And(children) | Self::Or(children) => {
43 for child in children {
44 child.try_for_each_field(visit)?;
45 }
46 Ok(())
47 }
48 Self::Not(child) => child.try_for_each_field(visit),
49 Self::Compare(compare) => visit(compare.field()),
50 Self::CompareFields(compare) => {
51 visit(compare.left_field())?;
52 visit(compare.right_field())
53 }
54 Self::IsNull { field }
55 | Self::IsNotNull { field }
56 | Self::IsMissing { field }
57 | Self::IsEmpty { field }
58 | Self::IsNotEmpty { field }
59 | Self::TextContains { field, .. }
60 | Self::TextContainsCi { field, .. } => visit(field),
61 }
62 }
63
64 #[must_use]
66 pub const fn and(preds: Vec<Self>) -> Self {
67 Self::And(preds)
68 }
69
70 #[must_use]
72 pub const fn or(preds: Vec<Self>) -> Self {
73 Self::Or(preds)
74 }
75
76 #[must_use]
78 #[expect(clippy::should_implement_trait)]
79 pub fn not(pred: Self) -> Self {
80 Self::Not(Box::new(pred))
81 }
82
83 #[must_use]
85 pub fn eq(field: String, value: Value) -> Self {
86 Self::Compare(ComparePredicate::eq(field, value))
87 }
88
89 #[must_use]
91 pub fn ne(field: String, value: Value) -> Self {
92 Self::Compare(ComparePredicate::ne(field, value))
93 }
94
95 #[must_use]
97 pub fn lt(field: String, value: Value) -> Self {
98 Self::Compare(ComparePredicate::lt(field, value))
99 }
100
101 #[must_use]
103 pub fn lte(field: String, value: Value) -> Self {
104 Self::Compare(ComparePredicate::lte(field, value))
105 }
106
107 #[must_use]
109 pub fn gt(field: String, value: Value) -> Self {
110 Self::Compare(ComparePredicate::gt(field, value))
111 }
112
113 #[must_use]
115 pub fn gte(field: String, value: Value) -> Self {
116 Self::Compare(ComparePredicate::gte(field, value))
117 }
118
119 #[must_use]
121 pub fn in_(field: String, values: Vec<Value>) -> Self {
122 Self::Compare(ComparePredicate::in_(field, values))
123 }
124
125 #[must_use]
127 pub fn not_in(field: String, values: Vec<Value>) -> Self {
128 Self::Compare(ComparePredicate::not_in(field, values))
129 }
130
131 #[must_use]
133 pub const fn is_not_null(field: String) -> Self {
134 Self::IsNotNull { field }
135 }
136
137 #[must_use]
139 pub fn between(field: String, lower: Value, upper: Value) -> Self {
140 Self::And(vec![
141 Self::gte(field.clone(), lower),
142 Self::lte(field, upper),
143 ])
144 }
145
146 #[must_use]
148 pub fn not_between(field: String, lower: Value, upper: Value) -> Self {
149 Self::Or(vec![Self::lt(field.clone(), lower), Self::gt(field, upper)])
150 }
151}
152
153impl BitAnd for Predicate {
154 type Output = Self;
155
156 fn bitand(self, rhs: Self) -> Self::Output {
157 Self::And(vec![self, rhs])
158 }
159}
160
161impl BitAnd for &Predicate {
162 type Output = Predicate;
163
164 fn bitand(self, rhs: Self) -> Self::Output {
165 Predicate::And(vec![self.clone(), rhs.clone()])
166 }
167}
168
169impl BitOr for Predicate {
170 type Output = Self;
171
172 fn bitor(self, rhs: Self) -> Self::Output {
173 Self::Or(vec![self, rhs])
174 }
175}
176
177impl BitOr for &Predicate {
178 type Output = Predicate;
179
180 fn bitor(self, rhs: Self) -> Self::Output {
181 Predicate::Or(vec![self.clone(), rhs.clone()])
182 }
183}
184
185#[cfg_attr(doc, doc = "CompareOp")]
186#[derive(Clone, Copy, Debug, Eq, PartialEq)]
187#[repr(u8)]
188pub enum CompareOp {
189 Eq = 0x01,
190 Ne = 0x02,
191 Lt = 0x03,
192 Lte = 0x04,
193 Gt = 0x05,
194 Gte = 0x06,
195 In = 0x07,
196 NotIn = 0x08,
197 Contains = 0x09,
198 StartsWith = 0x0a,
199 EndsWith = 0x0b,
200}
201
202impl CompareOp {
203 #[must_use]
205 pub const fn tag(self) -> u8 {
206 self as u8
207 }
208
209 #[must_use]
211 pub const fn is_equality_family(self) -> bool {
212 matches!(self, Self::Eq | Self::Ne)
213 }
214
215 #[must_use]
217 pub const fn is_ordering_family(self) -> bool {
218 matches!(self, Self::Lt | Self::Lte | Self::Gt | Self::Gte)
219 }
220
221 #[must_use]
223 pub const fn is_membership_family(self) -> bool {
224 matches!(self, Self::In | Self::NotIn)
225 }
226
227 #[must_use]
229 pub const fn is_contains_family(self) -> bool {
230 matches!(self, Self::Contains)
231 }
232
233 #[must_use]
235 pub const fn is_text_pattern_family(self) -> bool {
236 matches!(self, Self::StartsWith | Self::EndsWith)
237 }
238
239 #[must_use]
241 pub const fn supports_field_compare(self) -> bool {
242 self.is_equality_family() || self.is_ordering_family()
243 }
244
245 #[must_use]
248 pub const fn lower_bound_inclusive(self) -> Option<bool> {
249 match self {
250 Self::Gt => Some(false),
251 Self::Gte => Some(true),
252 Self::Eq
253 | Self::Ne
254 | Self::Lt
255 | Self::Lte
256 | Self::In
257 | Self::NotIn
258 | Self::Contains
259 | Self::StartsWith
260 | Self::EndsWith => None,
261 }
262 }
263
264 #[must_use]
267 pub const fn upper_bound_inclusive(self) -> Option<bool> {
268 match self {
269 Self::Lt => Some(false),
270 Self::Lte => Some(true),
271 Self::Eq
272 | Self::Ne
273 | Self::Gt
274 | Self::Gte
275 | Self::In
276 | Self::NotIn
277 | Self::Contains
278 | Self::StartsWith
279 | Self::EndsWith => None,
280 }
281 }
282
283 #[must_use]
285 pub const fn flipped(self) -> Self {
286 match self {
287 Self::Eq => Self::Eq,
288 Self::Ne => Self::Ne,
289 Self::Lt => Self::Gt,
290 Self::Lte => Self::Gte,
291 Self::Gt => Self::Lt,
292 Self::Gte => Self::Lte,
293 Self::In => Self::In,
294 Self::NotIn => Self::NotIn,
295 Self::Contains => Self::Contains,
296 Self::StartsWith => Self::StartsWith,
297 Self::EndsWith => Self::EndsWith,
298 }
299 }
300}
301
302#[cfg_attr(doc, doc = "ComparePredicate")]
303#[derive(Clone, Debug, Eq, PartialEq)]
304pub struct ComparePredicate {
305 pub(crate) field: String,
306 pub(crate) op: CompareOp,
307 pub(crate) value: Value,
308 pub(crate) coercion: CoercionSpec,
309}
310
311impl ComparePredicate {
312 fn new(field: String, op: CompareOp, value: Value) -> Self {
313 Self {
314 field,
315 op,
316 value,
317 coercion: CoercionSpec::default(),
318 }
319 }
320
321 #[must_use]
329 pub fn with_coercion(
330 field: impl Into<String>,
331 op: CompareOp,
332 value: Value,
333 coercion: CoercionId,
334 ) -> Self {
335 Self {
336 field: field.into(),
337 op,
338 value,
339 coercion: CoercionSpec::new(coercion),
340 }
341 }
342
343 #[must_use]
345 pub fn eq(field: String, value: Value) -> Self {
346 Self::new(field, CompareOp::Eq, value)
347 }
348
349 #[must_use]
351 pub fn ne(field: String, value: Value) -> Self {
352 Self::new(field, CompareOp::Ne, value)
353 }
354
355 #[must_use]
357 pub fn lt(field: String, value: Value) -> Self {
358 Self::new(field, CompareOp::Lt, value)
359 }
360
361 #[must_use]
363 pub fn lte(field: String, value: Value) -> Self {
364 Self::new(field, CompareOp::Lte, value)
365 }
366
367 #[must_use]
369 pub fn gt(field: String, value: Value) -> Self {
370 Self::new(field, CompareOp::Gt, value)
371 }
372
373 #[must_use]
375 pub fn gte(field: String, value: Value) -> Self {
376 Self::new(field, CompareOp::Gte, value)
377 }
378
379 #[must_use]
381 pub fn in_(field: String, values: Vec<Value>) -> Self {
382 Self::new(field, CompareOp::In, Value::List(values))
383 }
384
385 #[must_use]
387 pub fn not_in(field: String, values: Vec<Value>) -> Self {
388 Self::new(field, CompareOp::NotIn, Value::List(values))
389 }
390
391 #[must_use]
393 pub fn field(&self) -> &str {
394 &self.field
395 }
396
397 #[must_use]
399 pub const fn op(&self) -> CompareOp {
400 self.op
401 }
402
403 #[must_use]
405 pub const fn value(&self) -> &Value {
406 &self.value
407 }
408
409 #[must_use]
411 pub const fn coercion(&self) -> &CoercionSpec {
412 &self.coercion
413 }
414}
415
416#[derive(Clone, Debug, Eq, PartialEq)]
425pub struct CompareFieldsPredicate {
426 pub(crate) left_field: String,
427 pub(crate) op: CompareOp,
428 pub(crate) right_field: String,
429 pub(crate) coercion: CoercionSpec,
430}
431
432impl CompareFieldsPredicate {
433 fn canonicalize_symmetric_fields(
434 op: CompareOp,
435 left_field: String,
436 right_field: String,
437 ) -> (String, String) {
438 if op.is_equality_family() && left_field < right_field {
439 (right_field, left_field)
440 } else {
441 (left_field, right_field)
442 }
443 }
444
445 #[must_use]
453 pub fn with_coercion(
454 left_field: impl Into<String>,
455 op: CompareOp,
456 right_field: impl Into<String>,
457 coercion: CoercionId,
458 ) -> Self {
459 let (left_field, right_field) =
460 Self::canonicalize_symmetric_fields(op, left_field.into(), right_field.into());
461
462 Self {
463 left_field,
464 op,
465 right_field,
466 coercion: CoercionSpec::new(coercion),
467 }
468 }
469
470 #[must_use]
472 pub fn left_field(&self) -> &str {
473 &self.left_field
474 }
475
476 #[must_use]
478 pub const fn op(&self) -> CompareOp {
479 self.op
480 }
481
482 #[must_use]
484 pub fn right_field(&self) -> &str {
485 &self.right_field
486 }
487
488 #[must_use]
490 pub const fn coercion(&self) -> &CoercionSpec {
491 &self.coercion
492 }
493}
494
495#[cfg(test)]
500mod tests {
501 use super::*;
502
503 #[test]
504 fn borrowed_field_visit_stops_at_the_original_error() {
505 let predicate = Predicate::And(vec![
506 Predicate::eq("first".into(), Value::Text("not_a_field".into())),
507 Predicate::Not(Box::new(Predicate::IsNull {
508 field: "stop".into(),
509 })),
510 Predicate::IsEmpty {
511 field: "unvisited".into(),
512 },
513 ]);
514 let mut visited = Vec::new();
515 let result = predicate.try_for_each_field(&mut |field| {
516 visited.push(field.to_string());
517 if field == "stop" { Err(7_u8) } else { Ok(()) }
518 });
519 assert_eq!(result, Err(7));
520 assert_eq!(visited, ["first", "stop"]);
521 }
522
523 #[test]
524 fn compare_predicate_builders_preserve_operator_shape() {
525 assert_eq!(
526 Predicate::gt("age".to_string(), Value::Nat64(7)),
527 Predicate::Compare(ComparePredicate::gt("age".to_string(), Value::Nat64(7))),
528 );
529 }
530}
531
532crate::retained::retained_fields!(CompareFieldsPredicate {
534Self{left_field,op,right_field,coercion} => [left_field,op,right_field,coercion],
535});
536crate::retained::retained_copy!(CompareOp);
537crate::retained::retained_fields!(ComparePredicate {
538Self{field,op,value,coercion} => [field,op,value,coercion],
539});
540crate::retained::retained_fields!(Predicate {
541Self::True => [],
542Self::False => [],
543Self::And(field_0) => [field_0],
544Self::Or(field_0) => [field_0],
545Self::Not(field_0) => [field_0],
546Self::Compare(field_0) => [field_0],
547Self::CompareFields(field_0) => [field_0],
548Self::IsNull{field} => [field],
549Self::IsNotNull{field} => [field],
550Self::IsMissing{field} => [field],
551Self::IsEmpty{field} => [field],
552Self::IsNotEmpty{field} => [field],
553Self::TextContains{field,value} => [field,value],
554Self::TextContainsCi{field,value} => [field,value],
555});