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;
6#[cfg(feature = "postgis")]
7use super::spatial::SpatialFilter;
8use super::vector::VectorDistanceFilter;
9
10#[derive(Debug, Clone, PartialEq)]
11pub struct RelationFilter {
12    pub quantifier: RelationQuantifier,
13    pub parent_table: &'static str,
14    pub parent_column: &'static str,
15    pub related_table: &'static str,
16    pub related_column: &'static str,
17    pub filter: Box<FilterExpr>,
18    /// The related model's read scope, applied inside the subquery.
19    /// Required, never defaulted: see [`crate::RelatedReadScope`].
20    pub scope: crate::RelatedReadScope,
21}
22
23#[derive(Debug, Clone, PartialEq)]
24pub enum FilterExpr {
25    Filter(Filter),
26    All(Vec<FilterExpr>),
27    Any(Vec<FilterExpr>),
28    Not(Box<FilterExpr>),
29    Relation(RelationFilter),
30    /// `COALESCE(col_a, col_b, ...) op value` — built via
31    /// [`super::coalesce::coalesce`].
32    Coalesce(CoalesceFilter),
33    /// JSON / JSONB column predicates — see [`JsonFilter`]. Built via
34    /// `FieldRef::json_has_key(...)` and
35    /// `FieldRef::json_get_text(...).<cmp>(...)`.
36    Json(JsonFilter),
37    /// PostGIS spatial predicates — see [`SpatialFilter`]. Built via
38    /// `FieldRef::covers_geography(...)` /
39    /// `FieldRef::dwithin_geography(...)`. PG-only; the embedded
40    /// rusqlite backend doesn't ship SpatiaLite by default, so its
41    /// renderer fails loud at codegen time.
42    #[cfg(feature = "postgis")]
43    #[cfg(feature = "postgis")]
44    Spatial(SpatialFilter),
45    /// `Vector(n)` distance-to-a-query-vector threshold predicates (see
46    /// `docs/design/extensions.md` §6/§7, cratestack#163) — see
47    /// [`VectorDistanceFilter`]. Built via
48    /// `FieldRef::distance_to(...).lt(...)`/`.lte(...)`/etc. PG-only
49    /// (pgvector); the embedded rusqlite backend fails loud at render
50    /// time, mirroring [`Self::Spatial`].
51    VectorDistance(VectorDistanceFilter),
52}
53
54impl From<Filter> for FilterExpr {
55    fn from(value: Filter) -> Self {
56        Self::Filter(value)
57    }
58}
59
60impl RelationFilter {
61    pub fn new(
62        quantifier: RelationQuantifier,
63        parent_table: &'static str,
64        parent_column: &'static str,
65        related_table: &'static str,
66        related_column: &'static str,
67        filter: FilterExpr,
68        scope: crate::RelatedReadScope,
69    ) -> Self {
70        Self {
71            quantifier,
72            parent_table,
73            parent_column,
74            related_table,
75            related_column,
76            filter: Box::new(filter),
77            scope,
78        }
79    }
80}
81
82impl FilterExpr {
83    pub fn all(filters: impl IntoIterator<Item = FilterExpr>) -> Self {
84        Self::All(filters.into_iter().collect())
85    }
86
87    pub fn any(filters: impl IntoIterator<Item = FilterExpr>) -> Self {
88        Self::Any(filters.into_iter().collect())
89    }
90
91    // A builder-style combinator alongside `all`/`any`; intentionally a
92    // by-value method (with double-negation folding), not `ops::Not`.
93    #[allow(clippy::should_implement_trait)]
94    pub fn not(self) -> Self {
95        match self {
96            Self::Not(inner) => *inner,
97            inner => Self::Not(Box::new(inner)),
98        }
99    }
100
101    pub fn and(self, other: impl Into<FilterExpr>) -> Self {
102        match (self, other.into()) {
103            (Self::All(mut left), Self::All(right)) => {
104                left.extend(right);
105                Self::All(left)
106            }
107            (Self::All(mut left), right) => {
108                left.push(right);
109                Self::All(left)
110            }
111            (left, Self::All(mut right)) => {
112                let mut filters = vec![left];
113                filters.append(&mut right);
114                Self::All(filters)
115            }
116            (left, right) => Self::All(vec![left, right]),
117        }
118    }
119
120    pub fn or(self, other: impl Into<FilterExpr>) -> Self {
121        match (self, other.into()) {
122            (Self::Any(mut left), Self::Any(right)) => {
123                left.extend(right);
124                Self::Any(left)
125            }
126            (Self::Any(mut left), right) => {
127                left.push(right);
128                Self::Any(left)
129            }
130            (left, Self::Any(mut right)) => {
131                let mut filters = vec![left];
132                filters.append(&mut right);
133                Self::Any(filters)
134            }
135            (left, right) => Self::Any(vec![left, right]),
136        }
137    }
138}