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    // Internal helpers
39    // ------------------------------------------------------------------
40
41    /// Internal comparison predicate builder.
42    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    // ------------------------------------------------------------------
52    // Comparison predicates
53    // ------------------------------------------------------------------
54
55    /// Strict equality comparison (no coercion).
56    #[must_use]
57    pub fn eq(self, value: impl FieldValue) -> Predicate {
58        self.cmp(CompareOp::Eq, value, CoercionId::Strict)
59    }
60
61    /// Case-insensitive equality for text fields.
62    #[must_use]
63    pub fn text_eq_ci(self, value: impl FieldValue) -> Predicate {
64        self.cmp(CompareOp::Eq, value, CoercionId::TextCasefold)
65    }
66
67    /// Strict inequality comparison.
68    #[must_use]
69    pub fn ne(self, value: impl FieldValue) -> Predicate {
70        self.cmp(CompareOp::Ne, value, CoercionId::Strict)
71    }
72
73    /// Less-than comparison with numeric widening.
74    #[must_use]
75    pub fn lt(self, value: impl FieldValue) -> Predicate {
76        self.cmp(CompareOp::Lt, value, CoercionId::NumericWiden)
77    }
78
79    /// Less-than-or-equal comparison with numeric widening.
80    #[must_use]
81    pub fn lte(self, value: impl FieldValue) -> Predicate {
82        self.cmp(CompareOp::Lte, value, CoercionId::NumericWiden)
83    }
84
85    /// Greater-than comparison with numeric widening.
86    #[must_use]
87    pub fn gt(self, value: impl FieldValue) -> Predicate {
88        self.cmp(CompareOp::Gt, value, CoercionId::NumericWiden)
89    }
90
91    /// Greater-than-or-equal comparison with numeric widening.
92    #[must_use]
93    pub fn gte(self, value: impl FieldValue) -> Predicate {
94        self.cmp(CompareOp::Gte, value, CoercionId::NumericWiden)
95    }
96
97    /// Membership test against a fixed list (strict).
98    #[must_use]
99    pub fn in_list<I, V>(self, values: I) -> Predicate
100    where
101        I: IntoIterator<Item = V>,
102        V: FieldValue,
103    {
104        Predicate::Compare(ComparePredicate::with_coercion(
105            self.0,
106            CompareOp::In,
107            Value::List(values.into_iter().map(|v| v.to_value()).collect()),
108            CoercionId::Strict,
109        ))
110    }
111
112    // ------------------------------------------------------------------
113    // Structural predicates
114    // ------------------------------------------------------------------
115
116    /// Field is present and explicitly null.
117    #[must_use]
118    pub fn is_null(self) -> Predicate {
119        Predicate::IsNull {
120            field: self.0.to_string(),
121        }
122    }
123
124    /// Field is not present at all.
125    #[must_use]
126    pub fn is_missing(self) -> Predicate {
127        Predicate::IsMissing {
128            field: self.0.to_string(),
129        }
130    }
131
132    /// Field is present but empty (collection- or string-specific).
133    #[must_use]
134    pub fn is_empty(self) -> Predicate {
135        Predicate::IsEmpty {
136            field: self.0.to_string(),
137        }
138    }
139
140    /// Field is present and non-empty.
141    #[must_use]
142    pub fn is_not_empty(self) -> Predicate {
143        Predicate::IsNotEmpty {
144            field: self.0.to_string(),
145        }
146    }
147
148    /// Case-sensitive substring match for text fields.
149    #[must_use]
150    pub fn text_contains(self, value: impl FieldValue) -> Predicate {
151        Predicate::TextContains {
152            field: self.0.to_string(),
153            value: value.to_value(),
154        }
155    }
156
157    /// Case-insensitive substring match for text fields.
158    #[must_use]
159    pub fn text_contains_ci(self, value: impl FieldValue) -> Predicate {
160        Predicate::TextContainsCi {
161            field: self.0.to_string(),
162            value: value.to_value(),
163        }
164    }
165}
166
167// ----------------------------------------------------------------------
168// Boundary traits
169// ----------------------------------------------------------------------
170
171impl AsRef<str> for FieldRef {
172    fn as_ref(&self) -> &str {
173        self.0
174    }
175}