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 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 #[must_use]
34 pub const fn and(preds: Vec<Self>) -> Self {
35 Self::And(preds)
36 }
37
38 #[must_use]
40 pub const fn or(preds: Vec<Self>) -> Self {
41 Self::Or(preds)
42 }
43
44 #[must_use]
46 #[expect(clippy::should_implement_trait)]
47 pub fn not(pred: Self) -> Self {
48 Self::Not(Box::new(pred))
49 }
50
51 #[must_use]
53 pub fn eq(field: String, value: Value) -> Self {
54 Self::Compare(ComparePredicate::eq(field, value))
55 }
56
57 #[must_use]
59 pub fn ne(field: String, value: Value) -> Self {
60 Self::Compare(ComparePredicate::ne(field, value))
61 }
62
63 #[must_use]
65 pub fn lt(field: String, value: Value) -> Self {
66 Self::Compare(ComparePredicate::lt(field, value))
67 }
68
69 #[must_use]
71 pub fn lte(field: String, value: Value) -> Self {
72 Self::Compare(ComparePredicate::lte(field, value))
73 }
74
75 #[must_use]
77 pub fn gt(field: String, value: Value) -> Self {
78 Self::Compare(ComparePredicate::gt(field, value))
79 }
80
81 #[must_use]
83 pub fn gte(field: String, value: Value) -> Self {
84 Self::Compare(ComparePredicate::gte(field, value))
85 }
86
87 #[must_use]
89 pub fn in_(field: String, values: Vec<Value>) -> Self {
90 Self::Compare(ComparePredicate::in_(field, values))
91 }
92
93 #[must_use]
95 pub fn not_in(field: String, values: Vec<Value>) -> Self {
96 Self::Compare(ComparePredicate::not_in(field, values))
97 }
98
99 #[must_use]
101 pub const fn is_not_null(field: String) -> Self {
102 Self::IsNotNull { field }
103 }
104
105 #[must_use]
107 pub fn between(field: String, lower: Value, upper: Value) -> Self {
108 Self::And(vec![
109 Self::gte(field.clone(), lower),
110 Self::lte(field, upper),
111 ])
112 }
113}
114
115impl BitAnd for Predicate {
116 type Output = Self;
117
118 fn bitand(self, rhs: Self) -> Self::Output {
119 Self::And(vec![self, rhs])
120 }
121}
122
123impl BitAnd for &Predicate {
124 type Output = Predicate;
125
126 fn bitand(self, rhs: Self) -> Self::Output {
127 Predicate::And(vec![self.clone(), rhs.clone()])
128 }
129}
130
131impl BitOr for Predicate {
132 type Output = Self;
133
134 fn bitor(self, rhs: Self) -> Self::Output {
135 Self::Or(vec![self, rhs])
136 }
137}
138
139impl BitOr for &Predicate {
140 type Output = Predicate;
141
142 fn bitor(self, rhs: Self) -> Self::Output {
143 Predicate::Or(vec![self.clone(), rhs.clone()])
144 }
145}
146
147#[cfg_attr(
148 doc,
149 doc = "Neutral predicate model consumed by executor/index layers."
150)]
151pub(crate) type PredicateExecutionModel = Predicate;
152
153#[cfg_attr(doc, doc = "CompareOp")]
154#[derive(Clone, Copy, Debug, Eq, PartialEq)]
155#[repr(u8)]
156pub enum CompareOp {
157 Eq = 0x01,
158 Ne = 0x02,
159 Lt = 0x03,
160 Lte = 0x04,
161 Gt = 0x05,
162 Gte = 0x06,
163 In = 0x07,
164 NotIn = 0x08,
165 Contains = 0x09,
166 StartsWith = 0x0a,
167 EndsWith = 0x0b,
168}
169
170impl CompareOp {
171 #[must_use]
173 pub const fn tag(self) -> u8 {
174 self as u8
175 }
176}
177
178#[cfg_attr(doc, doc = "ComparePredicate")]
179#[derive(Clone, Debug, Eq, PartialEq)]
180pub struct ComparePredicate {
181 pub(crate) field: String,
182 pub(crate) op: CompareOp,
183 pub(crate) value: Value,
184 pub(crate) coercion: CoercionSpec,
185}
186
187impl ComparePredicate {
188 fn new(field: String, op: CompareOp, value: Value) -> Self {
189 Self {
190 field,
191 op,
192 value,
193 coercion: CoercionSpec::default(),
194 }
195 }
196
197 #[must_use]
199 pub fn with_coercion(
200 field: impl Into<String>,
201 op: CompareOp,
202 value: Value,
203 coercion: CoercionId,
204 ) -> Self {
205 Self {
206 field: field.into(),
207 op,
208 value,
209 coercion: CoercionSpec::new(coercion),
210 }
211 }
212
213 #[must_use]
215 pub fn eq(field: String, value: Value) -> Self {
216 Self::new(field, CompareOp::Eq, value)
217 }
218
219 #[must_use]
221 pub fn ne(field: String, value: Value) -> Self {
222 Self::new(field, CompareOp::Ne, value)
223 }
224
225 #[must_use]
227 pub fn lt(field: String, value: Value) -> Self {
228 Self::new(field, CompareOp::Lt, value)
229 }
230
231 #[must_use]
233 pub fn lte(field: String, value: Value) -> Self {
234 Self::new(field, CompareOp::Lte, value)
235 }
236
237 #[must_use]
239 pub fn gt(field: String, value: Value) -> Self {
240 Self::new(field, CompareOp::Gt, value)
241 }
242
243 #[must_use]
245 pub fn gte(field: String, value: Value) -> Self {
246 Self::new(field, CompareOp::Gte, value)
247 }
248
249 #[must_use]
251 pub fn in_(field: String, values: Vec<Value>) -> Self {
252 Self::new(field, CompareOp::In, Value::List(values))
253 }
254
255 #[must_use]
257 pub fn not_in(field: String, values: Vec<Value>) -> Self {
258 Self::new(field, CompareOp::NotIn, Value::List(values))
259 }
260
261 #[must_use]
263 pub fn field(&self) -> &str {
264 &self.field
265 }
266
267 #[must_use]
269 pub const fn op(&self) -> CompareOp {
270 self.op
271 }
272
273 #[must_use]
275 pub const fn value(&self) -> &Value {
276 &self.value
277 }
278
279 #[must_use]
281 pub const fn coercion(&self) -> &CoercionSpec {
282 &self.coercion
283 }
284}
285
286#[cfg_attr(
287 doc,
288 doc = "UnsupportedQueryFeature\n\nPolicy-level query features intentionally rejected by the engine."
289)]
290#[derive(Clone, Debug, Eq, PartialEq, ThisError)]
291pub enum UnsupportedQueryFeature {
292 #[error("map field '{field}' is not queryable; use scalar/indexed fields or list entries")]
293 MapPredicate { field: String },
294}