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
172impl BitAnd for Predicate {
173 type Output = Self;
174
175 fn bitand(self, rhs: Self) -> Self::Output {
176 Self::And(vec![self, rhs])
177 }
178}
179
180impl BitAnd for &Predicate {
181 type Output = Predicate;
182
183 fn bitand(self, rhs: Self) -> Self::Output {
184 Predicate::And(vec![self.clone(), rhs.clone()])
185 }
186}
187
188impl BitOr for Predicate {
189 type Output = Self;
190
191 fn bitor(self, rhs: Self) -> Self::Output {
192 Self::Or(vec![self, rhs])
193 }
194}
195
196impl BitOr for &Predicate {
197 type Output = Predicate;
198
199 fn bitor(self, rhs: Self) -> Self::Output {
200 Predicate::Or(vec![self.clone(), rhs.clone()])
201 }
202}
203
204#[cfg_attr(
205 doc,
206 doc = "Neutral predicate model consumed by executor/index layers."
207)]
208pub(crate) type PredicateExecutionModel = Predicate;
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
235#[cfg_attr(doc, doc = "ComparePredicate")]
236#[derive(Clone, Debug, Eq, PartialEq)]
237pub struct ComparePredicate {
238 pub(crate) field: String,
239 pub(crate) op: CompareOp,
240 pub(crate) value: Value,
241 pub(crate) coercion: CoercionSpec,
242}
243
244impl ComparePredicate {
245 fn new(field: String, op: CompareOp, value: Value) -> Self {
246 Self {
247 field,
248 op,
249 value,
250 coercion: CoercionSpec::default(),
251 }
252 }
253
254 #[must_use]
256 pub fn with_coercion(
257 field: impl Into<String>,
258 op: CompareOp,
259 value: Value,
260 coercion: CoercionId,
261 ) -> Self {
262 Self {
263 field: field.into(),
264 op,
265 value,
266 coercion: CoercionSpec::new(coercion),
267 }
268 }
269
270 #[must_use]
272 pub fn eq(field: String, value: Value) -> Self {
273 Self::new(field, CompareOp::Eq, value)
274 }
275
276 #[must_use]
278 pub fn ne(field: String, value: Value) -> Self {
279 Self::new(field, CompareOp::Ne, value)
280 }
281
282 #[must_use]
284 pub fn lt(field: String, value: Value) -> Self {
285 Self::new(field, CompareOp::Lt, value)
286 }
287
288 #[must_use]
290 pub fn lte(field: String, value: Value) -> Self {
291 Self::new(field, CompareOp::Lte, value)
292 }
293
294 #[must_use]
296 pub fn gt(field: String, value: Value) -> Self {
297 Self::new(field, CompareOp::Gt, value)
298 }
299
300 #[must_use]
302 pub fn gte(field: String, value: Value) -> Self {
303 Self::new(field, CompareOp::Gte, value)
304 }
305
306 #[must_use]
308 pub fn in_(field: String, values: Vec<Value>) -> Self {
309 Self::new(field, CompareOp::In, Value::List(values))
310 }
311
312 #[must_use]
314 pub fn not_in(field: String, values: Vec<Value>) -> Self {
315 Self::new(field, CompareOp::NotIn, Value::List(values))
316 }
317
318 #[must_use]
320 pub fn field(&self) -> &str {
321 &self.field
322 }
323
324 #[must_use]
326 pub const fn op(&self) -> CompareOp {
327 self.op
328 }
329
330 #[must_use]
332 pub const fn value(&self) -> &Value {
333 &self.value
334 }
335
336 #[must_use]
338 pub const fn coercion(&self) -> &CoercionSpec {
339 &self.coercion
340 }
341}
342
343#[derive(Clone, Debug, Eq, PartialEq)]
352pub struct CompareFieldsPredicate {
353 pub(crate) left_field: String,
354 pub(crate) op: CompareOp,
355 pub(crate) right_field: String,
356 pub(crate) coercion: CoercionSpec,
357}
358
359impl CompareFieldsPredicate {
360 fn new(left_field: String, op: CompareOp, right_field: String) -> Self {
361 Self {
362 left_field,
363 op,
364 right_field,
365 coercion: CoercionSpec::default(),
366 }
367 }
368
369 #[must_use]
372 pub fn with_coercion(
373 left_field: impl Into<String>,
374 op: CompareOp,
375 right_field: impl Into<String>,
376 coercion: CoercionId,
377 ) -> Self {
378 Self {
379 left_field: left_field.into(),
380 op,
381 right_field: right_field.into(),
382 coercion: CoercionSpec::new(coercion),
383 }
384 }
385
386 #[must_use]
388 pub fn eq(left_field: String, right_field: String) -> Self {
389 Self::new(left_field, CompareOp::Eq, right_field)
390 }
391
392 #[must_use]
394 pub fn ne(left_field: String, right_field: String) -> Self {
395 Self::new(left_field, CompareOp::Ne, right_field)
396 }
397
398 #[must_use]
400 pub fn lt(left_field: String, right_field: String) -> Self {
401 Self::new(left_field, CompareOp::Lt, right_field)
402 }
403
404 #[must_use]
406 pub fn lte(left_field: String, right_field: String) -> Self {
407 Self::new(left_field, CompareOp::Lte, right_field)
408 }
409
410 #[must_use]
412 pub fn gt(left_field: String, right_field: String) -> Self {
413 Self::new(left_field, CompareOp::Gt, right_field)
414 }
415
416 #[must_use]
418 pub fn gte(left_field: String, right_field: String) -> Self {
419 Self::new(left_field, CompareOp::Gte, right_field)
420 }
421
422 #[must_use]
424 pub fn left_field(&self) -> &str {
425 &self.left_field
426 }
427
428 #[must_use]
430 pub const fn op(&self) -> CompareOp {
431 self.op
432 }
433
434 #[must_use]
436 pub fn right_field(&self) -> &str {
437 &self.right_field
438 }
439
440 #[must_use]
442 pub const fn coercion(&self) -> &CoercionSpec {
443 &self.coercion
444 }
445}
446
447#[cfg_attr(
448 doc,
449 doc = "UnsupportedQueryFeature\n\nPolicy-level query features intentionally rejected by the engine."
450)]
451#[derive(Clone, Debug, Eq, PartialEq, ThisError)]
452pub enum UnsupportedQueryFeature {
453 #[error("map field '{field}' is not queryable; use scalar/indexed fields or list entries")]
454 MapPredicate { field: String },
455}