Skip to main content

icydb_core/db/query/builder/
field.rs

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