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