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