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