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 { field: String },
140    IsMissing { field: String },
141    IsEmpty { field: String },
142    IsNotEmpty { field: String },
143    TextContains { field: String, value: Value },
144    TextContainsCi { field: String, value: Value },
145}
146
147impl Predicate {
148    #[must_use]
149    pub const fn and(preds: Vec<Self>) -> Self {
150        Self::And(preds)
151    }
152
153    #[must_use]
154    pub const fn or(preds: Vec<Self>) -> Self {
155        Self::Or(preds)
156    }
157
158    #[must_use]
159    #[expect(clippy::should_implement_trait)]
160    pub fn not(pred: Self) -> Self {
161        Self::Not(Box::new(pred))
162    }
163
164    #[must_use]
165    pub fn eq(field: String, value: Value) -> Self {
166        Self::Compare(ComparePredicate::eq(field, value))
167    }
168
169    #[must_use]
170    pub fn ne(field: String, value: Value) -> Self {
171        Self::Compare(ComparePredicate::ne(field, value))
172    }
173
174    #[must_use]
175    pub fn lt(field: String, value: Value) -> Self {
176        Self::Compare(ComparePredicate::lt(field, value))
177    }
178
179    #[must_use]
180    pub fn lte(field: String, value: Value) -> Self {
181        Self::Compare(ComparePredicate::lte(field, value))
182    }
183
184    #[must_use]
185    pub fn gt(field: String, value: Value) -> Self {
186        Self::Compare(ComparePredicate::gt(field, value))
187    }
188
189    #[must_use]
190    pub fn gte(field: String, value: Value) -> Self {
191        Self::Compare(ComparePredicate::gte(field, value))
192    }
193
194    #[must_use]
195    pub fn in_(field: String, values: Vec<Value>) -> Self {
196        Self::Compare(ComparePredicate::in_(field, values))
197    }
198
199    #[must_use]
200    pub fn not_in(field: String, values: Vec<Value>) -> Self {
201        Self::Compare(ComparePredicate::not_in(field, values))
202    }
203}
204
205impl BitAnd for Predicate {
206    type Output = Self;
207
208    fn bitand(self, rhs: Self) -> Self::Output {
209        Self::And(vec![self, rhs])
210    }
211}
212
213impl BitAnd for &Predicate {
214    type Output = Predicate;
215
216    fn bitand(self, rhs: Self) -> Self::Output {
217        Predicate::And(vec![self.clone(), rhs.clone()])
218    }
219}
220
221impl BitOr for Predicate {
222    type Output = Self;
223
224    fn bitor(self, rhs: Self) -> Self::Output {
225        Self::Or(vec![self, rhs])
226    }
227}
228
229impl BitOr for &Predicate {
230    type Output = Predicate;
231
232    fn bitor(self, rhs: Self) -> Self::Output {
233        Predicate::Or(vec![self.clone(), rhs.clone()])
234    }
235}
236
237///
238/// UnsupportedQueryFeature
239///
240/// Policy-level query features that are intentionally not supported.
241///
242/// Map predicates are disallowed by design: map storage may exist, but query
243/// semantics over map structure are intentionally fenced for deterministic and
244/// stable query behavior.
245///
246
247#[derive(Clone, Debug, Eq, PartialEq, ThisError)]
248pub enum UnsupportedQueryFeature {
249    #[error(
250        "map field '{field}' is not queryable; map predicates are disabled. model queryable attributes as scalar/indexed fields or list entries"
251    )]
252    MapPredicate { field: String },
253}