1use crate::{
7 db::predicate::coercion::{CoercionId, CoercionSpec},
8 value::Value,
9};
10use std::ops::{BitAnd, BitOr};
11use thiserror::Error as ThisError;
12
13#[cfg_attr(doc, doc = "Predicate")]
14#[derive(Clone, Debug, Eq, PartialEq)]
15pub enum Predicate {
16 True,
17 False,
18 And(Vec<Self>),
19 Or(Vec<Self>),
20 Not(Box<Self>),
21 Compare(ComparePredicate),
22 CompareFields(CompareFieldsPredicate),
23 IsNull { field: String },
24 IsNotNull { field: String },
25 IsMissing { field: String },
26 IsEmpty { field: String },
27 IsNotEmpty { field: String },
28 TextContains { field: String, value: Value },
29 TextContainsCi { field: String, value: Value },
30}
31
32impl Predicate {
33 #[must_use]
35 pub const fn and(preds: Vec<Self>) -> Self {
36 Self::And(preds)
37 }
38
39 #[must_use]
41 pub const fn or(preds: Vec<Self>) -> Self {
42 Self::Or(preds)
43 }
44
45 #[must_use]
47 #[expect(clippy::should_implement_trait)]
48 pub fn not(pred: Self) -> Self {
49 Self::Not(Box::new(pred))
50 }
51
52 #[must_use]
54 pub fn eq(field: String, value: Value) -> Self {
55 Self::Compare(ComparePredicate::eq(field, value))
56 }
57
58 #[must_use]
60 pub fn ne(field: String, value: Value) -> Self {
61 Self::Compare(ComparePredicate::ne(field, value))
62 }
63
64 #[must_use]
66 pub fn lt(field: String, value: Value) -> Self {
67 Self::Compare(ComparePredicate::lt(field, value))
68 }
69
70 #[must_use]
72 pub fn lte(field: String, value: Value) -> Self {
73 Self::Compare(ComparePredicate::lte(field, value))
74 }
75
76 #[must_use]
78 pub fn gt(field: String, value: Value) -> Self {
79 Self::Compare(ComparePredicate::gt(field, value))
80 }
81
82 #[must_use]
84 pub fn gte(field: String, value: Value) -> Self {
85 Self::Compare(ComparePredicate::gte(field, value))
86 }
87
88 #[must_use]
90 pub fn eq_fields(left_field: String, right_field: String) -> Self {
91 Self::CompareFields(CompareFieldsPredicate::eq(left_field, right_field))
92 }
93
94 #[must_use]
96 pub fn ne_fields(left_field: String, right_field: String) -> Self {
97 Self::CompareFields(CompareFieldsPredicate::ne(left_field, right_field))
98 }
99
100 #[must_use]
102 pub fn lt_fields(left_field: String, right_field: String) -> Self {
103 Self::CompareFields(CompareFieldsPredicate::with_coercion(
104 left_field,
105 CompareOp::Lt,
106 right_field,
107 CoercionId::NumericWiden,
108 ))
109 }
110
111 #[must_use]
113 pub fn lte_fields(left_field: String, right_field: String) -> Self {
114 Self::CompareFields(CompareFieldsPredicate::with_coercion(
115 left_field,
116 CompareOp::Lte,
117 right_field,
118 CoercionId::NumericWiden,
119 ))
120 }
121
122 #[must_use]
124 pub fn gt_fields(left_field: String, right_field: String) -> Self {
125 Self::CompareFields(CompareFieldsPredicate::with_coercion(
126 left_field,
127 CompareOp::Gt,
128 right_field,
129 CoercionId::NumericWiden,
130 ))
131 }
132
133 #[must_use]
135 pub fn gte_fields(left_field: String, right_field: String) -> Self {
136 Self::CompareFields(CompareFieldsPredicate::with_coercion(
137 left_field,
138 CompareOp::Gte,
139 right_field,
140 CoercionId::NumericWiden,
141 ))
142 }
143
144 #[must_use]
146 pub fn in_(field: String, values: Vec<Value>) -> Self {
147 Self::Compare(ComparePredicate::in_(field, values))
148 }
149
150 #[must_use]
152 pub fn not_in(field: String, values: Vec<Value>) -> Self {
153 Self::Compare(ComparePredicate::not_in(field, values))
154 }
155
156 #[must_use]
158 pub const fn is_not_null(field: String) -> Self {
159 Self::IsNotNull { field }
160 }
161
162 #[must_use]
164 pub fn between(field: String, lower: Value, upper: Value) -> Self {
165 Self::And(vec![
166 Self::gte(field.clone(), lower),
167 Self::lte(field, upper),
168 ])
169 }
170
171 #[must_use]
173 pub fn not_between(field: String, lower: Value, upper: Value) -> Self {
174 Self::Or(vec![Self::lt(field.clone(), lower), Self::gt(field, upper)])
175 }
176}
177
178impl BitAnd for Predicate {
179 type Output = Self;
180
181 fn bitand(self, rhs: Self) -> Self::Output {
182 Self::And(vec![self, rhs])
183 }
184}
185
186impl BitAnd for &Predicate {
187 type Output = Predicate;
188
189 fn bitand(self, rhs: Self) -> Self::Output {
190 Predicate::And(vec![self.clone(), rhs.clone()])
191 }
192}
193
194impl BitOr for Predicate {
195 type Output = Self;
196
197 fn bitor(self, rhs: Self) -> Self::Output {
198 Self::Or(vec![self, rhs])
199 }
200}
201
202impl BitOr for &Predicate {
203 type Output = Predicate;
204
205 fn bitor(self, rhs: Self) -> Self::Output {
206 Predicate::Or(vec![self.clone(), rhs.clone()])
207 }
208}
209
210#[cfg_attr(doc, doc = "CompareOp")]
211#[derive(Clone, Copy, Debug, Eq, PartialEq)]
212#[repr(u8)]
213pub enum CompareOp {
214 Eq = 0x01,
215 Ne = 0x02,
216 Lt = 0x03,
217 Lte = 0x04,
218 Gt = 0x05,
219 Gte = 0x06,
220 In = 0x07,
221 NotIn = 0x08,
222 Contains = 0x09,
223 StartsWith = 0x0a,
224 EndsWith = 0x0b,
225}
226
227impl CompareOp {
228 #[must_use]
230 pub const fn tag(self) -> u8 {
231 self as u8
232 }
233
234 #[must_use]
236 pub const fn flipped(self) -> Self {
237 match self {
238 Self::Eq => Self::Eq,
239 Self::Ne => Self::Ne,
240 Self::Lt => Self::Gt,
241 Self::Lte => Self::Gte,
242 Self::Gt => Self::Lt,
243 Self::Gte => Self::Lte,
244 Self::In => Self::In,
245 Self::NotIn => Self::NotIn,
246 Self::Contains => Self::Contains,
247 Self::StartsWith => Self::StartsWith,
248 Self::EndsWith => Self::EndsWith,
249 }
250 }
251}
252
253#[cfg_attr(doc, doc = "ComparePredicate")]
254#[derive(Clone, Debug, Eq, PartialEq)]
255pub struct ComparePredicate {
256 pub(crate) field: String,
257 pub(crate) op: CompareOp,
258 pub(crate) value: Value,
259 pub(crate) coercion: CoercionSpec,
260}
261
262impl ComparePredicate {
263 fn new(field: String, op: CompareOp, value: Value) -> Self {
264 Self {
265 field,
266 op,
267 value,
268 coercion: CoercionSpec::default(),
269 }
270 }
271
272 #[must_use]
274 pub fn with_coercion(
275 field: impl Into<String>,
276 op: CompareOp,
277 value: Value,
278 coercion: CoercionId,
279 ) -> Self {
280 Self {
281 field: field.into(),
282 op,
283 value,
284 coercion: CoercionSpec::new(coercion),
285 }
286 }
287
288 #[must_use]
290 pub fn eq(field: String, value: Value) -> Self {
291 Self::new(field, CompareOp::Eq, value)
292 }
293
294 #[must_use]
296 pub fn ne(field: String, value: Value) -> Self {
297 Self::new(field, CompareOp::Ne, value)
298 }
299
300 #[must_use]
302 pub fn lt(field: String, value: Value) -> Self {
303 Self::new(field, CompareOp::Lt, value)
304 }
305
306 #[must_use]
308 pub fn lte(field: String, value: Value) -> Self {
309 Self::new(field, CompareOp::Lte, value)
310 }
311
312 #[must_use]
314 pub fn gt(field: String, value: Value) -> Self {
315 Self::new(field, CompareOp::Gt, value)
316 }
317
318 #[must_use]
320 pub fn gte(field: String, value: Value) -> Self {
321 Self::new(field, CompareOp::Gte, value)
322 }
323
324 #[must_use]
326 pub fn in_(field: String, values: Vec<Value>) -> Self {
327 Self::new(field, CompareOp::In, Value::List(values))
328 }
329
330 #[must_use]
332 pub fn not_in(field: String, values: Vec<Value>) -> Self {
333 Self::new(field, CompareOp::NotIn, Value::List(values))
334 }
335
336 #[must_use]
338 pub fn field(&self) -> &str {
339 &self.field
340 }
341
342 #[must_use]
344 pub const fn op(&self) -> CompareOp {
345 self.op
346 }
347
348 #[must_use]
350 pub const fn value(&self) -> &Value {
351 &self.value
352 }
353
354 #[must_use]
356 pub const fn coercion(&self) -> &CoercionSpec {
357 &self.coercion
358 }
359}
360
361#[derive(Clone, Debug, Eq, PartialEq)]
370pub struct CompareFieldsPredicate {
371 pub(crate) left_field: String,
372 pub(crate) op: CompareOp,
373 pub(crate) right_field: String,
374 pub(crate) coercion: CoercionSpec,
375}
376
377impl CompareFieldsPredicate {
378 fn canonicalize_symmetric_fields(
379 op: CompareOp,
380 left_field: String,
381 right_field: String,
382 ) -> (String, String) {
383 if matches!(op, CompareOp::Eq | CompareOp::Ne) && left_field < right_field {
384 (right_field, left_field)
385 } else {
386 (left_field, right_field)
387 }
388 }
389
390 fn new(left_field: String, op: CompareOp, right_field: String) -> Self {
391 let (left_field, right_field) =
392 Self::canonicalize_symmetric_fields(op, left_field, right_field);
393
394 Self {
395 left_field,
396 op,
397 right_field,
398 coercion: CoercionSpec::default(),
399 }
400 }
401
402 #[must_use]
405 pub fn with_coercion(
406 left_field: impl Into<String>,
407 op: CompareOp,
408 right_field: impl Into<String>,
409 coercion: CoercionId,
410 ) -> Self {
411 let (left_field, right_field) =
412 Self::canonicalize_symmetric_fields(op, left_field.into(), right_field.into());
413
414 Self {
415 left_field,
416 op,
417 right_field,
418 coercion: CoercionSpec::new(coercion),
419 }
420 }
421
422 #[must_use]
424 pub fn eq(left_field: String, right_field: String) -> Self {
425 Self::new(left_field, CompareOp::Eq, right_field)
426 }
427
428 #[must_use]
430 pub fn ne(left_field: String, right_field: String) -> Self {
431 Self::new(left_field, CompareOp::Ne, right_field)
432 }
433
434 #[must_use]
436 pub fn lt(left_field: String, right_field: String) -> Self {
437 Self::new(left_field, CompareOp::Lt, right_field)
438 }
439
440 #[must_use]
442 pub fn lte(left_field: String, right_field: String) -> Self {
443 Self::new(left_field, CompareOp::Lte, right_field)
444 }
445
446 #[must_use]
448 pub fn gt(left_field: String, right_field: String) -> Self {
449 Self::new(left_field, CompareOp::Gt, right_field)
450 }
451
452 #[must_use]
454 pub fn gte(left_field: String, right_field: String) -> Self {
455 Self::new(left_field, CompareOp::Gte, right_field)
456 }
457
458 #[must_use]
460 pub fn left_field(&self) -> &str {
461 &self.left_field
462 }
463
464 #[must_use]
466 pub const fn op(&self) -> CompareOp {
467 self.op
468 }
469
470 #[must_use]
472 pub fn right_field(&self) -> &str {
473 &self.right_field
474 }
475
476 #[must_use]
478 pub const fn coercion(&self) -> &CoercionSpec {
479 &self.coercion
480 }
481}
482
483#[cfg_attr(
484 doc,
485 doc = "UnsupportedQueryFeature\n\nPolicy-level query features intentionally rejected by the engine."
486)]
487#[derive(Clone, Debug, Eq, PartialEq, ThisError)]
488pub enum UnsupportedQueryFeature {
489 #[error("map field '{field}' is not queryable; use scalar/indexed fields or list entries")]
490 MapPredicate { field: String },
491}