icydb_core/db/query/builder/
field.rs1use crate::{
7 db::predicate::{CoercionId, CompareFieldsPredicate, CompareOp, ComparePredicate, Predicate},
8 traits::FieldValue,
9 value::Value,
10};
11use derive_more::Deref;
12
13#[derive(Clone, Copy, Deref, Eq, Hash, PartialEq)]
22pub struct FieldRef(&'static str);
23
24impl FieldRef {
25 #[must_use]
27 pub const fn new(name: &'static str) -> Self {
28 Self(name)
29 }
30
31 #[must_use]
33 pub const fn as_str(self) -> &'static str {
34 self.0
35 }
36
37 fn cmp(self, op: CompareOp, value: impl FieldValue, coercion: CoercionId) -> Predicate {
43 Predicate::Compare(ComparePredicate::with_coercion(
44 self.0,
45 op,
46 value.to_value(),
47 coercion,
48 ))
49 }
50
51 fn cmp_field(self, op: CompareOp, other: impl AsRef<str>, coercion: CoercionId) -> Predicate {
53 Predicate::CompareFields(CompareFieldsPredicate::with_coercion(
54 self.0,
55 op,
56 other.as_ref(),
57 coercion,
58 ))
59 }
60
61 #[must_use]
67 pub fn eq(self, value: impl FieldValue) -> Predicate {
68 self.cmp(CompareOp::Eq, value, CoercionId::Strict)
69 }
70
71 #[must_use]
73 pub fn text_eq_ci(self, value: impl FieldValue) -> Predicate {
74 self.cmp(CompareOp::Eq, value, CoercionId::TextCasefold)
75 }
76
77 #[must_use]
79 pub fn ne(self, value: impl FieldValue) -> Predicate {
80 self.cmp(CompareOp::Ne, value, CoercionId::Strict)
81 }
82
83 #[must_use]
85 pub fn lt(self, value: impl FieldValue) -> Predicate {
86 self.cmp(CompareOp::Lt, value, CoercionId::NumericWiden)
87 }
88
89 #[must_use]
91 pub fn lte(self, value: impl FieldValue) -> Predicate {
92 self.cmp(CompareOp::Lte, value, CoercionId::NumericWiden)
93 }
94
95 #[must_use]
97 pub fn gt(self, value: impl FieldValue) -> Predicate {
98 self.cmp(CompareOp::Gt, value, CoercionId::NumericWiden)
99 }
100
101 #[must_use]
103 pub fn gte(self, value: impl FieldValue) -> Predicate {
104 self.cmp(CompareOp::Gte, value, CoercionId::NumericWiden)
105 }
106
107 #[must_use]
109 pub fn eq_field(self, other: impl AsRef<str>) -> Predicate {
110 self.cmp_field(CompareOp::Eq, other, CoercionId::Strict)
111 }
112
113 #[must_use]
115 pub fn ne_field(self, other: impl AsRef<str>) -> Predicate {
116 self.cmp_field(CompareOp::Ne, other, CoercionId::Strict)
117 }
118
119 #[must_use]
121 pub fn lt_field(self, other: impl AsRef<str>) -> Predicate {
122 self.cmp_field(CompareOp::Lt, other, CoercionId::NumericWiden)
123 }
124
125 #[must_use]
127 pub fn lte_field(self, other: impl AsRef<str>) -> Predicate {
128 self.cmp_field(CompareOp::Lte, other, CoercionId::NumericWiden)
129 }
130
131 #[must_use]
133 pub fn gt_field(self, other: impl AsRef<str>) -> Predicate {
134 self.cmp_field(CompareOp::Gt, other, CoercionId::NumericWiden)
135 }
136
137 #[must_use]
139 pub fn gte_field(self, other: impl AsRef<str>) -> Predicate {
140 self.cmp_field(CompareOp::Gte, other, CoercionId::NumericWiden)
141 }
142
143 #[must_use]
145 pub fn in_list<I, V>(self, values: I) -> Predicate
146 where
147 I: IntoIterator<Item = V>,
148 V: FieldValue,
149 {
150 Predicate::Compare(ComparePredicate::with_coercion(
151 self.0,
152 CompareOp::In,
153 Value::List(values.into_iter().map(|v| v.to_value()).collect()),
154 CoercionId::Strict,
155 ))
156 }
157
158 #[must_use]
164 pub fn is_null(self) -> Predicate {
165 Predicate::IsNull {
166 field: self.0.to_string(),
167 }
168 }
169
170 #[must_use]
172 pub fn is_not_null(self) -> Predicate {
173 Predicate::IsNotNull {
174 field: self.0.to_string(),
175 }
176 }
177
178 #[must_use]
180 pub fn is_missing(self) -> Predicate {
181 Predicate::IsMissing {
182 field: self.0.to_string(),
183 }
184 }
185
186 #[must_use]
188 pub fn is_empty(self) -> Predicate {
189 Predicate::IsEmpty {
190 field: self.0.to_string(),
191 }
192 }
193
194 #[must_use]
196 pub fn is_not_empty(self) -> Predicate {
197 Predicate::IsNotEmpty {
198 field: self.0.to_string(),
199 }
200 }
201
202 #[must_use]
204 pub fn text_contains(self, value: impl FieldValue) -> Predicate {
205 Predicate::TextContains {
206 field: self.0.to_string(),
207 value: value.to_value(),
208 }
209 }
210
211 #[must_use]
213 pub fn text_contains_ci(self, value: impl FieldValue) -> Predicate {
214 Predicate::TextContainsCi {
215 field: self.0.to_string(),
216 value: value.to_value(),
217 }
218 }
219
220 #[must_use]
222 pub fn text_starts_with(self, value: impl FieldValue) -> Predicate {
223 self.cmp(CompareOp::StartsWith, value, CoercionId::Strict)
224 }
225
226 #[must_use]
228 pub fn text_starts_with_ci(self, value: impl FieldValue) -> Predicate {
229 self.cmp(CompareOp::StartsWith, value, CoercionId::TextCasefold)
230 }
231
232 #[must_use]
234 pub fn between(self, lower: impl FieldValue, upper: impl FieldValue) -> Predicate {
235 Predicate::and(vec![self.gte(lower), self.lte(upper)])
236 }
237}
238
239impl AsRef<str> for FieldRef {
244 fn as_ref(&self) -> &str {
245 self.0
246 }
247}
248
249#[cfg(test)]
254mod tests {
255 use super::*;
256
257 #[test]
258 fn field_ref_text_starts_with_uses_strict_prefix_compare() {
259 let predicate = FieldRef::new("name").text_starts_with("Al");
260 let Predicate::Compare(compare) = predicate else {
261 panic!("expected compare predicate");
262 };
263
264 assert_eq!(compare.field, "name");
265 assert_eq!(compare.op, CompareOp::StartsWith);
266 assert_eq!(compare.coercion.id, CoercionId::Strict);
267 assert_eq!(compare.value, Value::Text("Al".to_string()));
268 }
269
270 #[test]
271 fn field_ref_text_starts_with_ci_uses_casefold_prefix_compare() {
272 let predicate = FieldRef::new("name").text_starts_with_ci("AL");
273 let Predicate::Compare(compare) = predicate else {
274 panic!("expected compare predicate");
275 };
276
277 assert_eq!(compare.field, "name");
278 assert_eq!(compare.op, CompareOp::StartsWith);
279 assert_eq!(compare.coercion.id, CoercionId::TextCasefold);
280 assert_eq!(compare.value, Value::Text("AL".to_string()));
281 }
282
283 #[test]
284 fn field_ref_gt_field_builds_compare_fields_predicate() {
285 let predicate = FieldRef::new("age").gt_field("rank");
286 let Predicate::CompareFields(compare) = predicate else {
287 panic!("expected field-to-field compare predicate");
288 };
289
290 assert_eq!(compare.left_field(), "age");
291 assert_eq!(compare.op(), CompareOp::Gt);
292 assert_eq!(compare.right_field(), "rank");
293 assert_eq!(compare.coercion().id, CoercionId::NumericWiden);
294 }
295}