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