Skip to main content

icydb_core/db/predicate/
model.rs

1use crate::{
2    db::predicate::coercion::{CoercionId, CoercionSpec},
3    value::Value,
4};
5use std::ops::{BitAnd, BitOr};
6use thiserror::Error as ThisError;
7
8///
9/// Predicate
10///
11
12#[derive(Clone, Debug, Eq, PartialEq)]
13pub enum Predicate {
14    True,
15    False,
16    And(Vec<Self>),
17    Or(Vec<Self>),
18    Not(Box<Self>),
19    Compare(ComparePredicate),
20    IsNull { field: String },
21    IsMissing { field: String },
22    IsEmpty { field: String },
23    IsNotEmpty { field: String },
24    TextContains { field: String, value: Value },
25    TextContainsCi { field: String, value: Value },
26}
27
28impl Predicate {
29    #[must_use]
30    pub const fn and(preds: Vec<Self>) -> Self {
31        Self::And(preds)
32    }
33
34    #[must_use]
35    pub const fn or(preds: Vec<Self>) -> Self {
36        Self::Or(preds)
37    }
38
39    #[must_use]
40    #[expect(clippy::should_implement_trait)]
41    pub fn not(pred: Self) -> Self {
42        Self::Not(Box::new(pred))
43    }
44
45    #[must_use]
46    pub fn eq(field: String, value: Value) -> Self {
47        Self::Compare(ComparePredicate::eq(field, value))
48    }
49
50    #[must_use]
51    pub fn ne(field: String, value: Value) -> Self {
52        Self::Compare(ComparePredicate::ne(field, value))
53    }
54
55    #[must_use]
56    pub fn lt(field: String, value: Value) -> Self {
57        Self::Compare(ComparePredicate::lt(field, value))
58    }
59
60    #[must_use]
61    pub fn lte(field: String, value: Value) -> Self {
62        Self::Compare(ComparePredicate::lte(field, value))
63    }
64
65    #[must_use]
66    pub fn gt(field: String, value: Value) -> Self {
67        Self::Compare(ComparePredicate::gt(field, value))
68    }
69
70    #[must_use]
71    pub fn gte(field: String, value: Value) -> Self {
72        Self::Compare(ComparePredicate::gte(field, value))
73    }
74
75    #[must_use]
76    pub fn in_(field: String, values: Vec<Value>) -> Self {
77        Self::Compare(ComparePredicate::in_(field, values))
78    }
79
80    #[must_use]
81    pub fn not_in(field: String, values: Vec<Value>) -> Self {
82        Self::Compare(ComparePredicate::not_in(field, values))
83    }
84}
85
86impl BitAnd for Predicate {
87    type Output = Self;
88
89    fn bitand(self, rhs: Self) -> Self::Output {
90        Self::And(vec![self, rhs])
91    }
92}
93
94impl BitAnd for &Predicate {
95    type Output = Predicate;
96
97    fn bitand(self, rhs: Self) -> Self::Output {
98        Predicate::And(vec![self.clone(), rhs.clone()])
99    }
100}
101
102impl BitOr for Predicate {
103    type Output = Self;
104
105    fn bitor(self, rhs: Self) -> Self::Output {
106        Self::Or(vec![self, rhs])
107    }
108}
109
110impl BitOr for &Predicate {
111    type Output = Predicate;
112
113    fn bitor(self, rhs: Self) -> Self::Output {
114        Predicate::Or(vec![self.clone(), rhs.clone()])
115    }
116}
117
118/// Neutral predicate model consumed by executor/index layers.
119pub(crate) type PredicateExecutionModel = Predicate;
120
121///
122/// CompareOp
123///
124
125#[derive(Clone, Copy, Debug, Eq, PartialEq)]
126#[repr(u8)]
127pub enum CompareOp {
128    Eq = 0x01,
129    Ne = 0x02,
130    Lt = 0x03,
131    Lte = 0x04,
132    Gt = 0x05,
133    Gte = 0x06,
134    In = 0x07,
135    NotIn = 0x08,
136    Contains = 0x09,
137    StartsWith = 0x0a,
138    EndsWith = 0x0b,
139}
140
141impl CompareOp {
142    #[must_use]
143    pub const fn tag(self) -> u8 {
144        self as u8
145    }
146}
147
148///
149/// ComparePredicate
150///
151
152#[derive(Clone, Debug, Eq, PartialEq)]
153pub struct ComparePredicate {
154    pub field: String,
155    pub op: CompareOp,
156    pub value: Value,
157    pub coercion: CoercionSpec,
158}
159
160impl ComparePredicate {
161    fn new(field: String, op: CompareOp, value: Value) -> Self {
162        Self {
163            field,
164            op,
165            value,
166            coercion: CoercionSpec::default(),
167        }
168    }
169
170    /// Construct a comparison predicate with an explicit coercion policy.
171    #[must_use]
172    pub fn with_coercion(
173        field: impl Into<String>,
174        op: CompareOp,
175        value: Value,
176        coercion: CoercionId,
177    ) -> Self {
178        Self {
179            field: field.into(),
180            op,
181            value,
182            coercion: CoercionSpec::new(coercion),
183        }
184    }
185
186    #[must_use]
187    pub fn eq(field: String, value: Value) -> Self {
188        Self::new(field, CompareOp::Eq, value)
189    }
190
191    #[must_use]
192    pub fn ne(field: String, value: Value) -> Self {
193        Self::new(field, CompareOp::Ne, value)
194    }
195
196    #[must_use]
197    pub fn lt(field: String, value: Value) -> Self {
198        Self::new(field, CompareOp::Lt, value)
199    }
200
201    #[must_use]
202    pub fn lte(field: String, value: Value) -> Self {
203        Self::new(field, CompareOp::Lte, value)
204    }
205
206    #[must_use]
207    pub fn gt(field: String, value: Value) -> Self {
208        Self::new(field, CompareOp::Gt, value)
209    }
210
211    #[must_use]
212    pub fn gte(field: String, value: Value) -> Self {
213        Self::new(field, CompareOp::Gte, value)
214    }
215
216    #[must_use]
217    pub fn in_(field: String, values: Vec<Value>) -> Self {
218        Self::new(field, CompareOp::In, Value::List(values))
219    }
220
221    #[must_use]
222    pub fn not_in(field: String, values: Vec<Value>) -> Self {
223        Self::new(field, CompareOp::NotIn, Value::List(values))
224    }
225}
226
227///
228/// UnsupportedQueryFeature
229///
230/// Policy-level query features intentionally rejected by the engine.
231///
232
233#[derive(Clone, Debug, Eq, PartialEq, ThisError)]
234pub enum UnsupportedQueryFeature {
235    #[error(
236        "map field '{field}' is not queryable; map predicates are disabled. model queryable attributes as scalar/indexed fields or list entries"
237    )]
238    MapPredicate { field: String },
239}