Skip to main content

icydb_core/db/query/predicate/
ast.rs

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