Skip to main content

icydb_core/db/query/builder/
field.rs

1use crate::{
2    db::query::predicate::{CoercionId, CoercionSpec, CompareOp, ComparePredicate, Predicate},
3    traits::FieldValue,
4    value::Value,
5};
6
7///
8/// FieldRef
9///
10/// Zero-cost wrapper around a static field name used in predicates.
11/// Enables method-based predicate builders without allocating.
12/// Carries only a `&'static str` and derefs to `str`.
13///
14
15#[derive(Clone, Copy, Eq, Hash, PartialEq)]
16pub struct FieldRef(&'static str);
17
18impl FieldRef {
19    /// Create a new field reference.
20    #[must_use]
21    pub const fn new(name: &'static str) -> Self {
22        Self(name)
23    }
24
25    /// Return the underlying field name.
26    #[must_use]
27    pub const fn as_str(self) -> &'static str {
28        self.0
29    }
30
31    // ------------------------------------------------------------------
32    // Comparison predicates
33    // ------------------------------------------------------------------
34
35    /// Strict equality comparison (no coercion).
36    #[must_use]
37    pub fn eq(self, value: impl FieldValue) -> Predicate {
38        compare(self.0, CompareOp::Eq, value.to_value(), CoercionId::Strict)
39    }
40
41    /// Case-insensitive text equality.
42    #[must_use]
43    pub fn eq_ci(self, value: impl FieldValue) -> Predicate {
44        compare(
45            self.0,
46            CompareOp::Eq,
47            value.to_value(),
48            CoercionId::TextCasefold,
49        )
50    }
51
52    /// Strict inequality comparison.
53    #[must_use]
54    pub fn ne(self, value: impl FieldValue) -> Predicate {
55        compare(self.0, CompareOp::Ne, value.to_value(), CoercionId::Strict)
56    }
57
58    /// Less-than comparison with numeric widening.
59    #[must_use]
60    pub fn lt(self, value: impl FieldValue) -> Predicate {
61        compare(
62            self.0,
63            CompareOp::Lt,
64            value.to_value(),
65            CoercionId::NumericWiden,
66        )
67    }
68
69    /// Less-than-or-equal comparison with numeric widening.
70    #[must_use]
71    pub fn lte(self, value: impl FieldValue) -> Predicate {
72        compare(
73            self.0,
74            CompareOp::Lte,
75            value.to_value(),
76            CoercionId::NumericWiden,
77        )
78    }
79
80    /// Greater-than comparison with numeric widening.
81    #[must_use]
82    pub fn gt(self, value: impl FieldValue) -> Predicate {
83        compare(
84            self.0,
85            CompareOp::Gt,
86            value.to_value(),
87            CoercionId::NumericWiden,
88        )
89    }
90
91    /// Greater-than-or-equal comparison with numeric widening.
92    #[must_use]
93    pub fn gte(self, value: impl FieldValue) -> Predicate {
94        compare(
95            self.0,
96            CompareOp::Gte,
97            value.to_value(),
98            CoercionId::NumericWiden,
99        )
100    }
101
102    /// Membership test against a fixed list (strict).
103    #[must_use]
104    pub fn in_list<I, V>(self, values: I) -> Predicate
105    where
106        I: IntoIterator<Item = V>,
107        V: FieldValue,
108    {
109        Predicate::Compare(ComparePredicate {
110            field: self.0.to_string(),
111            op: CompareOp::In,
112            value: Value::List(values.into_iter().map(|v| v.to_value()).collect()),
113            coercion: CoercionSpec::new(CoercionId::Strict),
114        })
115    }
116
117    // ------------------------------------------------------------------
118    // Structural predicates
119    // ------------------------------------------------------------------
120
121    /// Field is present and explicitly null.
122    #[must_use]
123    pub fn is_null(self) -> Predicate {
124        Predicate::IsNull {
125            field: self.0.to_string(),
126        }
127    }
128
129    /// Field is not present at all.
130    #[must_use]
131    pub fn is_missing(self) -> Predicate {
132        Predicate::IsMissing {
133            field: self.0.to_string(),
134        }
135    }
136
137    /// Field is present but empty (collection- or string-specific).
138    #[must_use]
139    pub fn is_empty(self) -> Predicate {
140        Predicate::IsEmpty {
141            field: self.0.to_string(),
142        }
143    }
144
145    /// Field is present and non-empty.
146    #[must_use]
147    pub fn is_not_empty(self) -> Predicate {
148        Predicate::IsNotEmpty {
149            field: self.0.to_string(),
150        }
151    }
152
153    // ------------------------------------------------------------------
154    // Map predicates
155    // ------------------------------------------------------------------
156
157    /// Map field contains the given key.
158    #[must_use]
159    pub fn map_contains_key(self, key: impl FieldValue, coercion: CoercionId) -> Predicate {
160        Predicate::MapContainsKey {
161            field: self.0.to_string(),
162            key: key.to_value(),
163            coercion: CoercionSpec::new(coercion),
164        }
165    }
166
167    /// Map field contains the given value.
168    #[must_use]
169    pub fn map_contains_value(self, value: impl FieldValue, coercion: CoercionId) -> Predicate {
170        Predicate::MapContainsValue {
171            field: self.0.to_string(),
172            value: value.to_value(),
173            coercion: CoercionSpec::new(coercion),
174        }
175    }
176
177    /// Map field contains the given key/value pair.
178    #[must_use]
179    pub fn map_contains_entry(
180        self,
181        key: impl FieldValue,
182        value: impl FieldValue,
183        coercion: CoercionId,
184    ) -> Predicate {
185        Predicate::MapContainsEntry {
186            field: self.0.to_string(),
187            key: key.to_value(),
188            value: value.to_value(),
189            coercion: CoercionSpec::new(coercion),
190        }
191    }
192
193    /// Case-sensitive substring match for text fields.
194    #[must_use]
195    pub fn text_contains(self, value: impl FieldValue) -> Predicate {
196        Predicate::TextContains {
197            field: self.0.to_string(),
198            value: value.to_value(),
199        }
200    }
201
202    /// Case-insensitive substring match for text fields.
203    #[must_use]
204    pub fn text_contains_ci(self, value: impl FieldValue) -> Predicate {
205        Predicate::TextContainsCi {
206            field: self.0.to_string(),
207            value: value.to_value(),
208        }
209    }
210}
211
212// ----------------------------------------------------------------------
213// Boundary traits
214// ----------------------------------------------------------------------
215
216impl AsRef<str> for FieldRef {
217    fn as_ref(&self) -> &str {
218        self.0
219    }
220}
221
222impl std::ops::Deref for FieldRef {
223    type Target = str;
224
225    fn deref(&self) -> &Self::Target {
226        self.0
227    }
228}
229
230// ----------------------------------------------------------------------
231// Internal helpers (not public API)
232// ----------------------------------------------------------------------
233
234fn compare(field: &str, op: CompareOp, value: Value, coercion: CoercionId) -> Predicate {
235    Predicate::Compare(ComparePredicate {
236        field: field.to_string(),
237        op,
238        value,
239        coercion: CoercionSpec::new(coercion),
240    })
241}