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        Predicate::Compare(ComparePredicate::with_coercion(
39            self.0,
40            CompareOp::Eq,
41            value.to_value(),
42            CoercionId::Strict,
43        ))
44    }
45
46    /// Case-insensitive equality for text fields.
47    #[must_use]
48    pub fn text_eq_ci(self, value: impl FieldValue) -> Predicate {
49        Predicate::Compare(ComparePredicate::with_coercion(
50            self.0,
51            CompareOp::Eq,
52            value.to_value(),
53            CoercionId::TextCasefold,
54        ))
55    }
56
57    /// Strict inequality comparison.
58    #[must_use]
59    pub fn ne(self, value: impl FieldValue) -> Predicate {
60        Predicate::Compare(ComparePredicate::with_coercion(
61            self.0,
62            CompareOp::Ne,
63            value.to_value(),
64            CoercionId::Strict,
65        ))
66    }
67
68    /// Less-than comparison with numeric widening.
69    #[must_use]
70    pub fn lt(self, value: impl FieldValue) -> Predicate {
71        Predicate::Compare(ComparePredicate::with_coercion(
72            self.0,
73            CompareOp::Lt,
74            value.to_value(),
75            CoercionId::NumericWiden,
76        ))
77    }
78
79    /// Less-than-or-equal comparison with numeric widening.
80    #[must_use]
81    pub fn lte(self, value: impl FieldValue) -> Predicate {
82        Predicate::Compare(ComparePredicate::with_coercion(
83            self.0,
84            CompareOp::Lte,
85            value.to_value(),
86            CoercionId::NumericWiden,
87        ))
88    }
89
90    /// Greater-than comparison with numeric widening.
91    #[must_use]
92    pub fn gt(self, value: impl FieldValue) -> Predicate {
93        Predicate::Compare(ComparePredicate::with_coercion(
94            self.0,
95            CompareOp::Gt,
96            value.to_value(),
97            CoercionId::NumericWiden,
98        ))
99    }
100
101    /// Greater-than-or-equal comparison with numeric widening.
102    #[must_use]
103    pub fn gte(self, value: impl FieldValue) -> Predicate {
104        Predicate::Compare(ComparePredicate::with_coercion(
105            self.0,
106            CompareOp::Gte,
107            value.to_value(),
108            CoercionId::NumericWiden,
109        ))
110    }
111
112    /// Membership test against a fixed list (strict).
113    #[must_use]
114    pub fn in_list<I, V>(self, values: I) -> Predicate
115    where
116        I: IntoIterator<Item = V>,
117        V: FieldValue,
118    {
119        Predicate::Compare(ComparePredicate::with_coercion(
120            self.0,
121            CompareOp::In,
122            Value::List(values.into_iter().map(|v| v.to_value()).collect()),
123            CoercionId::Strict,
124        ))
125    }
126
127    // ------------------------------------------------------------------
128    // Structural predicates
129    // ------------------------------------------------------------------
130
131    /// Field is present and explicitly null.
132    #[must_use]
133    pub fn is_null(self) -> Predicate {
134        Predicate::IsNull {
135            field: self.0.to_string(),
136        }
137    }
138
139    /// Field is not present at all.
140    #[must_use]
141    pub fn is_missing(self) -> Predicate {
142        Predicate::IsMissing {
143            field: self.0.to_string(),
144        }
145    }
146
147    /// Field is present but empty (collection- or string-specific).
148    #[must_use]
149    pub fn is_empty(self) -> Predicate {
150        Predicate::IsEmpty {
151            field: self.0.to_string(),
152        }
153    }
154
155    /// Field is present and non-empty.
156    #[must_use]
157    pub fn is_not_empty(self) -> Predicate {
158        Predicate::IsNotEmpty {
159            field: self.0.to_string(),
160        }
161    }
162
163    // ------------------------------------------------------------------
164    // Map predicates
165    // ------------------------------------------------------------------
166
167    /// Map field contains the given key.
168    #[must_use]
169    pub fn map_contains_key(self, key: impl FieldValue, coercion: CoercionId) -> Predicate {
170        Predicate::MapContainsKey {
171            field: self.0.to_string(),
172            key: key.to_value(),
173            coercion: CoercionSpec::new(coercion),
174        }
175    }
176
177    /// Map field contains the given value.
178    #[must_use]
179    pub fn map_contains_value(self, value: impl FieldValue, coercion: CoercionId) -> Predicate {
180        Predicate::MapContainsValue {
181            field: self.0.to_string(),
182            value: value.to_value(),
183            coercion: CoercionSpec::new(coercion),
184        }
185    }
186
187    /// Map field contains the given key/value pair.
188    #[must_use]
189    pub fn map_contains_entry(
190        self,
191        key: impl FieldValue,
192        value: impl FieldValue,
193        coercion: CoercionId,
194    ) -> Predicate {
195        Predicate::MapContainsEntry {
196            field: self.0.to_string(),
197            key: key.to_value(),
198            value: value.to_value(),
199            coercion: CoercionSpec::new(coercion),
200        }
201    }
202
203    /// Case-sensitive substring match for text fields.
204    #[must_use]
205    pub fn text_contains(self, value: impl FieldValue) -> Predicate {
206        Predicate::TextContains {
207            field: self.0.to_string(),
208            value: value.to_value(),
209        }
210    }
211
212    /// Case-insensitive substring match for text fields.
213    #[must_use]
214    pub fn text_contains_ci(self, value: impl FieldValue) -> Predicate {
215        Predicate::TextContainsCi {
216            field: self.0.to_string(),
217            value: value.to_value(),
218        }
219    }
220}
221
222// ----------------------------------------------------------------------
223// Boundary traits
224// ----------------------------------------------------------------------
225
226impl AsRef<str> for FieldRef {
227    fn as_ref(&self) -> &str {
228        self.0
229    }
230}
231
232impl std::ops::Deref for FieldRef {
233    type Target = str;
234
235    fn deref(&self) -> &Self::Target {
236        self.0
237    }
238}