Skip to main content

cratestack_sqlx/
filter.rs

1use std::marker::PhantomData;
2
3pub use cratestack_policy::RelationQuantifier;
4
5use crate::{IntoSqlValue, SqlValue, order::OrderTarget, values::FilterValue};
6
7#[derive(Debug, Clone, Copy)]
8pub struct FieldRef<M, T> {
9    column: &'static str,
10    _marker: PhantomData<fn() -> (M, T)>,
11}
12
13impl<M, T> FieldRef<M, T> {
14    pub const fn new(column: &'static str) -> Self {
15        Self {
16            column,
17            _marker: PhantomData,
18        }
19    }
20
21    pub fn asc(self) -> crate::OrderClause {
22        crate::OrderClause {
23            target: OrderTarget::Column(self.column),
24            direction: crate::SortDirection::Asc,
25        }
26    }
27
28    pub fn desc(self) -> crate::OrderClause {
29        crate::OrderClause {
30            target: OrderTarget::Column(self.column),
31            direction: crate::SortDirection::Desc,
32        }
33    }
34}
35
36impl<M, T> FieldRef<M, T> {
37    pub fn eq<V>(self, value: V) -> Filter
38    where
39        V: IntoSqlValue,
40    {
41        Filter::single(self.column, FilterOp::Eq, value)
42    }
43
44    pub fn ne<V>(self, value: V) -> Filter
45    where
46        V: IntoSqlValue,
47    {
48        Filter::single(self.column, FilterOp::Ne, value)
49    }
50
51    pub fn in_<I, V>(self, values: I) -> Filter
52    where
53        I: IntoIterator<Item = V>,
54        V: IntoSqlValue,
55    {
56        Filter {
57            column: self.column,
58            op: FilterOp::In,
59            value: FilterValue::Many(
60                values
61                    .into_iter()
62                    .map(IntoSqlValue::into_sql_value)
63                    .collect(),
64            ),
65        }
66    }
67
68    pub fn lt<V>(self, value: V) -> Filter
69    where
70        V: IntoSqlValue,
71    {
72        Filter::single(self.column, FilterOp::Lt, value)
73    }
74
75    pub fn lte<V>(self, value: V) -> Filter
76    where
77        V: IntoSqlValue,
78    {
79        Filter::single(self.column, FilterOp::Lte, value)
80    }
81
82    pub fn gt<V>(self, value: V) -> Filter
83    where
84        V: IntoSqlValue,
85    {
86        Filter::single(self.column, FilterOp::Gt, value)
87    }
88
89    pub fn gte<V>(self, value: V) -> Filter
90    where
91        V: IntoSqlValue,
92    {
93        Filter::single(self.column, FilterOp::Gte, value)
94    }
95}
96
97impl<M> FieldRef<M, bool> {
98    pub fn is_true(self) -> Filter {
99        self.eq(true)
100    }
101
102    pub fn is_false(self) -> Filter {
103        self.eq(false)
104    }
105}
106
107impl<M> FieldRef<M, String> {
108    pub fn contains(self, value: impl Into<String>) -> Filter {
109        Filter::string_pattern(self.column, FilterOp::Contains, "%{}%", value)
110    }
111
112    pub fn starts_with(self, value: impl Into<String>) -> Filter {
113        Filter::string_pattern(self.column, FilterOp::StartsWith, "{}%", value)
114    }
115}
116
117impl<M, T> FieldRef<M, Option<T>> {
118    pub fn is_null(self) -> Filter {
119        Filter {
120            column: self.column,
121            op: FilterOp::IsNull,
122            value: FilterValue::None,
123        }
124    }
125
126    pub fn is_not_null(self) -> Filter {
127        Filter {
128            column: self.column,
129            op: FilterOp::IsNotNull,
130            value: FilterValue::None,
131        }
132    }
133}
134
135impl<M> FieldRef<M, Option<String>> {
136    pub fn contains(self, value: impl Into<String>) -> Filter {
137        Filter::string_pattern(self.column, FilterOp::Contains, "%{}%", value)
138    }
139
140    pub fn starts_with(self, value: impl Into<String>) -> Filter {
141        Filter::string_pattern(self.column, FilterOp::StartsWith, "{}%", value)
142    }
143}
144
145#[derive(Debug, Clone, PartialEq)]
146pub struct Filter {
147    pub(crate) column: &'static str,
148    pub(crate) op: FilterOp,
149    pub(crate) value: FilterValue,
150}
151
152impl Filter {
153    fn single<V>(column: &'static str, op: FilterOp, value: V) -> Self
154    where
155        V: IntoSqlValue,
156    {
157        Self {
158            column,
159            op,
160            value: FilterValue::Single(value.into_sql_value()),
161        }
162    }
163
164    fn string_pattern(
165        column: &'static str,
166        op: FilterOp,
167        pattern: &str,
168        value: impl Into<String>,
169    ) -> Self {
170        Self {
171            column,
172            op,
173            value: FilterValue::Single(SqlValue::String(pattern.replacen("{}", &value.into(), 1))),
174        }
175    }
176}
177
178#[derive(Debug, Clone, PartialEq)]
179pub struct RelationFilter {
180    pub(crate) quantifier: RelationQuantifier,
181    pub(crate) parent_table: &'static str,
182    pub(crate) parent_column: &'static str,
183    pub(crate) related_table: &'static str,
184    pub(crate) related_column: &'static str,
185    pub(crate) filter: Box<FilterExpr>,
186}
187
188#[derive(Debug, Clone, PartialEq)]
189pub enum FilterExpr {
190    Filter(Filter),
191    All(Vec<FilterExpr>),
192    Any(Vec<FilterExpr>),
193    Not(Box<FilterExpr>),
194    Relation(RelationFilter),
195}
196
197impl From<Filter> for FilterExpr {
198    fn from(value: Filter) -> Self {
199        Self::Filter(value)
200    }
201}
202
203impl RelationFilter {
204    pub fn new(
205        quantifier: RelationQuantifier,
206        parent_table: &'static str,
207        parent_column: &'static str,
208        related_table: &'static str,
209        related_column: &'static str,
210        filter: FilterExpr,
211    ) -> Self {
212        Self {
213            quantifier,
214            parent_table,
215            parent_column,
216            related_table,
217            related_column,
218            filter: Box::new(filter),
219        }
220    }
221}
222
223impl FilterExpr {
224    pub fn all(filters: impl IntoIterator<Item = FilterExpr>) -> Self {
225        Self::All(filters.into_iter().collect())
226    }
227
228    pub fn any(filters: impl IntoIterator<Item = FilterExpr>) -> Self {
229        Self::Any(filters.into_iter().collect())
230    }
231
232    pub fn not(self) -> Self {
233        match self {
234            Self::Not(inner) => *inner,
235            inner => Self::Not(Box::new(inner)),
236        }
237    }
238
239    pub fn and(self, other: impl Into<FilterExpr>) -> Self {
240        match (self, other.into()) {
241            (Self::All(mut left), Self::All(right)) => {
242                left.extend(right);
243                Self::All(left)
244            }
245            (Self::All(mut left), right) => {
246                left.push(right);
247                Self::All(left)
248            }
249            (left, Self::All(mut right)) => {
250                let mut filters = vec![left];
251                filters.append(&mut right);
252                Self::All(filters)
253            }
254            (left, right) => Self::All(vec![left, right]),
255        }
256    }
257
258    pub fn or(self, other: impl Into<FilterExpr>) -> Self {
259        match (self, other.into()) {
260            (Self::Any(mut left), Self::Any(right)) => {
261                left.extend(right);
262                Self::Any(left)
263            }
264            (Self::Any(mut left), right) => {
265                left.push(right);
266                Self::Any(left)
267            }
268            (left, Self::Any(mut right)) => {
269                let mut filters = vec![left];
270                filters.append(&mut right);
271                Self::Any(filters)
272            }
273            (left, right) => Self::Any(vec![left, right]),
274        }
275    }
276
277    pub fn relation(
278        parent_table: &'static str,
279        parent_column: &'static str,
280        related_table: &'static str,
281        related_column: &'static str,
282        filter: FilterExpr,
283    ) -> Self {
284        Self::Relation(RelationFilter::new(
285            RelationQuantifier::ToOne,
286            parent_table,
287            parent_column,
288            related_table,
289            related_column,
290            filter,
291        ))
292    }
293
294    pub fn relation_some(
295        parent_table: &'static str,
296        parent_column: &'static str,
297        related_table: &'static str,
298        related_column: &'static str,
299        filter: FilterExpr,
300    ) -> Self {
301        Self::Relation(RelationFilter::new(
302            RelationQuantifier::Some,
303            parent_table,
304            parent_column,
305            related_table,
306            related_column,
307            filter,
308        ))
309    }
310
311    pub fn relation_every(
312        parent_table: &'static str,
313        parent_column: &'static str,
314        related_table: &'static str,
315        related_column: &'static str,
316        filter: FilterExpr,
317    ) -> Self {
318        Self::Relation(RelationFilter::new(
319            RelationQuantifier::Every,
320            parent_table,
321            parent_column,
322            related_table,
323            related_column,
324            filter,
325        ))
326    }
327
328    pub fn relation_none(
329        parent_table: &'static str,
330        parent_column: &'static str,
331        related_table: &'static str,
332        related_column: &'static str,
333        filter: FilterExpr,
334    ) -> Self {
335        Self::Relation(RelationFilter::new(
336            RelationQuantifier::None,
337            parent_table,
338            parent_column,
339            related_table,
340            related_column,
341            filter,
342        ))
343    }
344}
345
346#[derive(Debug, Clone, Copy, PartialEq, Eq)]
347pub(crate) enum FilterOp {
348    Eq,
349    Ne,
350    Lt,
351    Lte,
352    Gt,
353    Gte,
354    In,
355    Contains,
356    StartsWith,
357    IsNull,
358    IsNotNull,
359}