Skip to main content

icydb_core/db/predicate/
model.rs

1//! Module: predicate::model
2//! Responsibility: public predicate AST and construction helpers.
3//! Does not own: schema validation or runtime slot resolution.
4//! Boundary: user/query-facing predicate model.
5
6use crate::{
7    db::predicate::coercion::{CoercionId, CoercionSpec},
8    value::Value,
9};
10use std::ops::{BitAnd, BitOr};
11use thiserror::Error as ThisError;
12
13///
14/// Predicate
15///
16
17#[derive(Clone, Debug, Eq, PartialEq)]
18pub enum Predicate {
19    True,
20    False,
21    And(Vec<Self>),
22    Or(Vec<Self>),
23    Not(Box<Self>),
24    Compare(ComparePredicate),
25    IsNull { field: String },
26    IsNotNull { field: String },
27    IsMissing { field: String },
28    IsEmpty { field: String },
29    IsNotEmpty { field: String },
30    TextContains { field: String, value: Value },
31    TextContainsCi { field: String, value: Value },
32}
33
34impl Predicate {
35    /// Build an `And` predicate from child predicates.
36    #[must_use]
37    pub const fn and(preds: Vec<Self>) -> Self {
38        Self::And(preds)
39    }
40
41    /// Build an `Or` predicate from child predicates.
42    #[must_use]
43    pub const fn or(preds: Vec<Self>) -> Self {
44        Self::Or(preds)
45    }
46
47    /// Negate one predicate.
48    #[must_use]
49    #[expect(clippy::should_implement_trait)]
50    pub fn not(pred: Self) -> Self {
51        Self::Not(Box::new(pred))
52    }
53
54    /// Compare `field == value`.
55    #[must_use]
56    pub fn eq(field: String, value: Value) -> Self {
57        Self::Compare(ComparePredicate::eq(field, value))
58    }
59
60    /// Compare `field != value`.
61    #[must_use]
62    pub fn ne(field: String, value: Value) -> Self {
63        Self::Compare(ComparePredicate::ne(field, value))
64    }
65
66    /// Compare `field < value`.
67    #[must_use]
68    pub fn lt(field: String, value: Value) -> Self {
69        Self::Compare(ComparePredicate::lt(field, value))
70    }
71
72    /// Compare `field <= value`.
73    #[must_use]
74    pub fn lte(field: String, value: Value) -> Self {
75        Self::Compare(ComparePredicate::lte(field, value))
76    }
77
78    /// Compare `field > value`.
79    #[must_use]
80    pub fn gt(field: String, value: Value) -> Self {
81        Self::Compare(ComparePredicate::gt(field, value))
82    }
83
84    /// Compare `field >= value`.
85    #[must_use]
86    pub fn gte(field: String, value: Value) -> Self {
87        Self::Compare(ComparePredicate::gte(field, value))
88    }
89
90    /// Compare `field IN values`.
91    #[must_use]
92    pub fn in_(field: String, values: Vec<Value>) -> Self {
93        Self::Compare(ComparePredicate::in_(field, values))
94    }
95
96    /// Compare `field NOT IN values`.
97    #[must_use]
98    pub fn not_in(field: String, values: Vec<Value>) -> Self {
99        Self::Compare(ComparePredicate::not_in(field, values))
100    }
101
102    /// Compare `field IS NOT NULL`.
103    #[must_use]
104    pub const fn is_not_null(field: String) -> Self {
105        Self::IsNotNull { field }
106    }
107
108    /// Compare `field BETWEEN lower AND upper`.
109    #[must_use]
110    pub fn between(field: String, lower: Value, upper: Value) -> Self {
111        Self::And(vec![
112            Self::gte(field.clone(), lower),
113            Self::lte(field, upper),
114        ])
115    }
116}
117
118impl BitAnd for Predicate {
119    type Output = Self;
120
121    fn bitand(self, rhs: Self) -> Self::Output {
122        Self::And(vec![self, rhs])
123    }
124}
125
126impl BitAnd for &Predicate {
127    type Output = Predicate;
128
129    fn bitand(self, rhs: Self) -> Self::Output {
130        Predicate::And(vec![self.clone(), rhs.clone()])
131    }
132}
133
134impl BitOr for Predicate {
135    type Output = Self;
136
137    fn bitor(self, rhs: Self) -> Self::Output {
138        Self::Or(vec![self, rhs])
139    }
140}
141
142impl BitOr for &Predicate {
143    type Output = Predicate;
144
145    fn bitor(self, rhs: Self) -> Self::Output {
146        Predicate::Or(vec![self.clone(), rhs.clone()])
147    }
148}
149
150/// Neutral predicate model consumed by executor/index layers.
151pub(crate) type PredicateExecutionModel = Predicate;
152
153///
154/// CompareOp
155///
156
157#[derive(Clone, Copy, Debug, Eq, PartialEq)]
158#[repr(u8)]
159pub enum CompareOp {
160    Eq = 0x01,
161    Ne = 0x02,
162    Lt = 0x03,
163    Lte = 0x04,
164    Gt = 0x05,
165    Gte = 0x06,
166    In = 0x07,
167    NotIn = 0x08,
168    Contains = 0x09,
169    StartsWith = 0x0a,
170    EndsWith = 0x0b,
171}
172
173impl CompareOp {
174    /// Return the stable wire tag for this compare operator.
175    #[must_use]
176    pub const fn tag(self) -> u8 {
177        self as u8
178    }
179}
180
181///
182/// ComparePredicate
183///
184
185#[derive(Clone, Debug, Eq, PartialEq)]
186pub struct ComparePredicate {
187    pub(crate) field: String,
188    pub(crate) op: CompareOp,
189    pub(crate) value: Value,
190    pub(crate) coercion: CoercionSpec,
191}
192
193impl ComparePredicate {
194    fn new(field: String, op: CompareOp, value: Value) -> Self {
195        Self {
196            field,
197            op,
198            value,
199            coercion: CoercionSpec::default(),
200        }
201    }
202
203    /// Construct a comparison predicate with an explicit coercion policy.
204    #[must_use]
205    pub fn with_coercion(
206        field: impl Into<String>,
207        op: CompareOp,
208        value: Value,
209        coercion: CoercionId,
210    ) -> Self {
211        Self {
212            field: field.into(),
213            op,
214            value,
215            coercion: CoercionSpec::new(coercion),
216        }
217    }
218
219    /// Build `Eq` comparison.
220    #[must_use]
221    pub fn eq(field: String, value: Value) -> Self {
222        Self::new(field, CompareOp::Eq, value)
223    }
224
225    /// Build `Ne` comparison.
226    #[must_use]
227    pub fn ne(field: String, value: Value) -> Self {
228        Self::new(field, CompareOp::Ne, value)
229    }
230
231    /// Build `Lt` comparison.
232    #[must_use]
233    pub fn lt(field: String, value: Value) -> Self {
234        Self::new(field, CompareOp::Lt, value)
235    }
236
237    /// Build `Lte` comparison.
238    #[must_use]
239    pub fn lte(field: String, value: Value) -> Self {
240        Self::new(field, CompareOp::Lte, value)
241    }
242
243    /// Build `Gt` comparison.
244    #[must_use]
245    pub fn gt(field: String, value: Value) -> Self {
246        Self::new(field, CompareOp::Gt, value)
247    }
248
249    /// Build `Gte` comparison.
250    #[must_use]
251    pub fn gte(field: String, value: Value) -> Self {
252        Self::new(field, CompareOp::Gte, value)
253    }
254
255    /// Build `In` comparison.
256    #[must_use]
257    pub fn in_(field: String, values: Vec<Value>) -> Self {
258        Self::new(field, CompareOp::In, Value::List(values))
259    }
260
261    /// Build `NotIn` comparison.
262    #[must_use]
263    pub fn not_in(field: String, values: Vec<Value>) -> Self {
264        Self::new(field, CompareOp::NotIn, Value::List(values))
265    }
266
267    /// Borrow the compared field name.
268    #[must_use]
269    pub fn field(&self) -> &str {
270        &self.field
271    }
272
273    /// Return the compare operator.
274    #[must_use]
275    pub const fn op(&self) -> CompareOp {
276        self.op
277    }
278
279    /// Borrow the compared literal value.
280    #[must_use]
281    pub const fn value(&self) -> &Value {
282        &self.value
283    }
284
285    /// Borrow the comparison coercion policy.
286    #[must_use]
287    pub const fn coercion(&self) -> &CoercionSpec {
288        &self.coercion
289    }
290}
291
292///
293/// UnsupportedQueryFeature
294///
295/// Policy-level query features intentionally rejected by the engine.
296///
297
298#[derive(Clone, Debug, Eq, PartialEq, ThisError)]
299pub enum UnsupportedQueryFeature {
300    #[error(
301        "map field '{field}' is not queryable; map predicates are disabled. model queryable attributes as scalar/indexed fields or list entries"
302    )]
303    MapPredicate { field: String },
304}