Skip to main content

cratestack_sql/filter/
expr.rs

1pub use cratestack_policy::RelationQuantifier;
2
3use super::coalesce::CoalesceFilter;
4use super::filter::Filter;
5use super::json::JsonFilter;
6use super::spatial::SpatialFilter;
7
8#[derive(Debug, Clone, PartialEq)]
9pub struct RelationFilter {
10    pub quantifier: RelationQuantifier,
11    pub parent_table: &'static str,
12    pub parent_column: &'static str,
13    pub related_table: &'static str,
14    pub related_column: &'static str,
15    pub filter: Box<FilterExpr>,
16}
17
18#[derive(Debug, Clone, PartialEq)]
19pub enum FilterExpr {
20    Filter(Filter),
21    All(Vec<FilterExpr>),
22    Any(Vec<FilterExpr>),
23    Not(Box<FilterExpr>),
24    Relation(RelationFilter),
25    /// `COALESCE(col_a, col_b, ...) op value` — built via
26    /// [`super::coalesce::coalesce`].
27    Coalesce(CoalesceFilter),
28    /// JSON / JSONB column predicates — see [`JsonFilter`]. Built via
29    /// `FieldRef::json_has_key(...)` and
30    /// `FieldRef::json_get_text(...).<cmp>(...)`.
31    Json(JsonFilter),
32    /// PostGIS spatial predicates — see [`SpatialFilter`]. Built via
33    /// `FieldRef::covers_geography(...)` /
34    /// `FieldRef::dwithin_geography(...)`. PG-only; the embedded
35    /// rusqlite backend doesn't ship SpatiaLite by default, so its
36    /// renderer fails loud at codegen time.
37    Spatial(SpatialFilter),
38}
39
40impl From<Filter> for FilterExpr {
41    fn from(value: Filter) -> Self {
42        Self::Filter(value)
43    }
44}
45
46impl RelationFilter {
47    pub fn new(
48        quantifier: RelationQuantifier,
49        parent_table: &'static str,
50        parent_column: &'static str,
51        related_table: &'static str,
52        related_column: &'static str,
53        filter: FilterExpr,
54    ) -> Self {
55        Self {
56            quantifier,
57            parent_table,
58            parent_column,
59            related_table,
60            related_column,
61            filter: Box::new(filter),
62        }
63    }
64}
65
66impl FilterExpr {
67    pub fn all(filters: impl IntoIterator<Item = FilterExpr>) -> Self {
68        Self::All(filters.into_iter().collect())
69    }
70
71    pub fn any(filters: impl IntoIterator<Item = FilterExpr>) -> Self {
72        Self::Any(filters.into_iter().collect())
73    }
74
75    // A builder-style combinator alongside `all`/`any`; intentionally a
76    // by-value method (with double-negation folding), not `ops::Not`.
77    #[allow(clippy::should_implement_trait)]
78    pub fn not(self) -> Self {
79        match self {
80            Self::Not(inner) => *inner,
81            inner => Self::Not(Box::new(inner)),
82        }
83    }
84
85    pub fn and(self, other: impl Into<FilterExpr>) -> Self {
86        match (self, other.into()) {
87            (Self::All(mut left), Self::All(right)) => {
88                left.extend(right);
89                Self::All(left)
90            }
91            (Self::All(mut left), right) => {
92                left.push(right);
93                Self::All(left)
94            }
95            (left, Self::All(mut right)) => {
96                let mut filters = vec![left];
97                filters.append(&mut right);
98                Self::All(filters)
99            }
100            (left, right) => Self::All(vec![left, right]),
101        }
102    }
103
104    pub fn or(self, other: impl Into<FilterExpr>) -> Self {
105        match (self, other.into()) {
106            (Self::Any(mut left), Self::Any(right)) => {
107                left.extend(right);
108                Self::Any(left)
109            }
110            (Self::Any(mut left), right) => {
111                left.push(right);
112                Self::Any(left)
113            }
114            (left, Self::Any(mut right)) => {
115                let mut filters = vec![left];
116                filters.append(&mut right);
117                Self::Any(filters)
118            }
119            (left, right) => Self::Any(vec![left, right]),
120        }
121    }
122
123    pub fn relation(
124        parent_table: &'static str,
125        parent_column: &'static str,
126        related_table: &'static str,
127        related_column: &'static str,
128        filter: FilterExpr,
129    ) -> Self {
130        Self::Relation(RelationFilter::new(
131            RelationQuantifier::ToOne,
132            parent_table,
133            parent_column,
134            related_table,
135            related_column,
136            filter,
137        ))
138    }
139
140    pub fn relation_some(
141        parent_table: &'static str,
142        parent_column: &'static str,
143        related_table: &'static str,
144        related_column: &'static str,
145        filter: FilterExpr,
146    ) -> Self {
147        Self::Relation(RelationFilter::new(
148            RelationQuantifier::Some,
149            parent_table,
150            parent_column,
151            related_table,
152            related_column,
153            filter,
154        ))
155    }
156
157    pub fn relation_every(
158        parent_table: &'static str,
159        parent_column: &'static str,
160        related_table: &'static str,
161        related_column: &'static str,
162        filter: FilterExpr,
163    ) -> Self {
164        Self::Relation(RelationFilter::new(
165            RelationQuantifier::Every,
166            parent_table,
167            parent_column,
168            related_table,
169            related_column,
170            filter,
171        ))
172    }
173
174    pub fn relation_none(
175        parent_table: &'static str,
176        parent_column: &'static str,
177        related_table: &'static str,
178        related_column: &'static str,
179        filter: FilterExpr,
180    ) -> Self {
181        Self::Relation(RelationFilter::new(
182            RelationQuantifier::None,
183            parent_table,
184            parent_column,
185            related_table,
186            related_column,
187            filter,
188        ))
189    }
190}