icydb_core/db/query/builder/
field.rs1use crate::db::query::expr::{FilterExpr, FilterValue};
7
8#[derive(Clone, Copy, Eq, Hash, PartialEq)]
17pub struct FieldRef(&'static str);
18
19impl FieldRef {
20 #[must_use]
22 pub const fn new(name: &'static str) -> Self {
23 Self(name)
24 }
25
26 #[must_use]
28 pub const fn as_str(self) -> &'static str {
29 self.0
30 }
31
32 fn cmp_field(
34 self,
35 other: impl AsRef<str>,
36 build: impl FnOnce(String, String) -> FilterExpr,
37 ) -> FilterExpr {
38 build(self.0.to_string(), other.as_ref().to_string())
39 }
40
41 #[must_use]
47 pub fn eq(self, value: impl Into<FilterValue>) -> FilterExpr {
48 FilterExpr::eq(self.0, value)
49 }
50
51 #[must_use]
53 pub fn text_eq_ci(self, value: impl Into<FilterValue>) -> FilterExpr {
54 FilterExpr::eq_ci(self.0, value)
55 }
56
57 #[must_use]
59 pub fn ne(self, value: impl Into<FilterValue>) -> FilterExpr {
60 FilterExpr::ne(self.0, value)
61 }
62
63 #[must_use]
65 pub fn lt(self, value: impl Into<FilterValue>) -> FilterExpr {
66 FilterExpr::lt(self.0, value)
67 }
68
69 #[must_use]
71 pub fn lte(self, value: impl Into<FilterValue>) -> FilterExpr {
72 FilterExpr::lte(self.0, value)
73 }
74
75 #[must_use]
77 pub fn gt(self, value: impl Into<FilterValue>) -> FilterExpr {
78 FilterExpr::gt(self.0, value)
79 }
80
81 #[must_use]
83 pub fn gte(self, value: impl Into<FilterValue>) -> FilterExpr {
84 FilterExpr::gte(self.0, value)
85 }
86
87 #[must_use]
89 pub fn eq_field(self, other: impl AsRef<str>) -> FilterExpr {
90 self.cmp_field(other, FilterExpr::eq_field)
91 }
92
93 #[must_use]
95 pub fn ne_field(self, other: impl AsRef<str>) -> FilterExpr {
96 self.cmp_field(other, FilterExpr::ne_field)
97 }
98
99 #[must_use]
101 pub fn lt_field(self, other: impl AsRef<str>) -> FilterExpr {
102 self.cmp_field(other, FilterExpr::lt_field)
103 }
104
105 #[must_use]
107 pub fn lte_field(self, other: impl AsRef<str>) -> FilterExpr {
108 self.cmp_field(other, FilterExpr::lte_field)
109 }
110
111 #[must_use]
113 pub fn gt_field(self, other: impl AsRef<str>) -> FilterExpr {
114 self.cmp_field(other, FilterExpr::gt_field)
115 }
116
117 #[must_use]
119 pub fn gte_field(self, other: impl AsRef<str>) -> FilterExpr {
120 self.cmp_field(other, FilterExpr::gte_field)
121 }
122
123 #[must_use]
125 pub fn in_list<I, V>(self, values: I) -> FilterExpr
126 where
127 I: IntoIterator<Item = V>,
128 V: Into<FilterValue>,
129 {
130 FilterExpr::in_list(self.0, values)
131 }
132
133 #[must_use]
139 pub fn is_null(self) -> FilterExpr {
140 FilterExpr::is_null(self.0)
141 }
142
143 #[must_use]
145 pub fn is_not_null(self) -> FilterExpr {
146 FilterExpr::is_not_null(self.0)
147 }
148
149 #[must_use]
151 pub fn is_missing(self) -> FilterExpr {
152 FilterExpr::is_missing(self.0)
153 }
154
155 #[must_use]
157 pub fn is_empty(self) -> FilterExpr {
158 FilterExpr::is_empty(self.0)
159 }
160
161 #[must_use]
163 pub fn is_not_empty(self) -> FilterExpr {
164 FilterExpr::is_not_empty(self.0)
165 }
166
167 #[must_use]
169 pub fn text_contains(self, value: impl Into<FilterValue>) -> FilterExpr {
170 FilterExpr::text_contains(self.0, value)
171 }
172
173 #[must_use]
175 pub fn text_contains_ci(self, value: impl Into<FilterValue>) -> FilterExpr {
176 FilterExpr::text_contains_ci(self.0, value)
177 }
178
179 #[must_use]
181 pub fn text_starts_with(self, value: impl Into<FilterValue>) -> FilterExpr {
182 FilterExpr::starts_with(self.0, value)
183 }
184
185 #[must_use]
187 pub fn text_starts_with_ci(self, value: impl Into<FilterValue>) -> FilterExpr {
188 FilterExpr::starts_with_ci(self.0, value)
189 }
190
191 #[must_use]
193 pub fn between(
194 self,
195 lower: impl Into<FilterValue>,
196 upper: impl Into<FilterValue>,
197 ) -> FilterExpr {
198 FilterExpr::and(vec![self.gte(lower), self.lte(upper)])
199 }
200
201 #[must_use]
203 pub fn between_fields(self, lower: impl AsRef<str>, upper: impl AsRef<str>) -> FilterExpr {
204 FilterExpr::and(vec![self.gte_field(lower), self.lte_field(upper)])
205 }
206
207 #[must_use]
209 pub fn not_between(
210 self,
211 lower: impl Into<FilterValue>,
212 upper: impl Into<FilterValue>,
213 ) -> FilterExpr {
214 FilterExpr::or(vec![self.lt(lower), self.gt(upper)])
215 }
216
217 #[must_use]
219 pub fn not_between_fields(self, lower: impl AsRef<str>, upper: impl AsRef<str>) -> FilterExpr {
220 FilterExpr::or(vec![self.lt_field(lower), self.gt_field(upper)])
221 }
222}
223
224impl AsRef<str> for FieldRef {
229 fn as_ref(&self) -> &str {
230 self.0
231 }
232}
233
234#[cfg(test)]
239mod tests {
240 use super::{FieldRef, FilterExpr};
241
242 #[test]
243 fn field_ref_text_starts_with_uses_strict_prefix_compare() {
244 assert_eq!(
245 FieldRef::new("name").text_starts_with("Al"),
246 FilterExpr::starts_with("name", "Al"),
247 );
248 }
249
250 #[test]
251 fn field_ref_text_starts_with_ci_uses_casefold_prefix_compare() {
252 assert_eq!(
253 FieldRef::new("name").text_starts_with_ci("AL"),
254 FilterExpr::starts_with_ci("name", "AL"),
255 );
256 }
257
258 #[test]
259 fn field_ref_gt_field_builds_field_compare_filter_expr() {
260 assert_eq!(
261 FieldRef::new("age").gt_field("rank"),
262 FilterExpr::gt_field("age", "rank"),
263 );
264 }
265
266 #[test]
267 fn field_ref_not_between_builds_outside_range_filter_expr() {
268 assert_eq!(
269 FieldRef::new("age").not_between(10_u64, 20_u64),
270 FilterExpr::or(vec![
271 FilterExpr::lt("age", 10_u64),
272 FilterExpr::gt("age", 20_u64),
273 ])
274 );
275 }
276
277 #[test]
278 fn field_ref_between_fields_builds_field_bound_range_filter_expr() {
279 assert_eq!(
280 FieldRef::new("age").between_fields("min_age", "max_age"),
281 FilterExpr::and(vec![
282 FilterExpr::gte_field("age", "min_age"),
283 FilterExpr::lte_field("age", "max_age"),
284 ])
285 );
286 }
287}