Skip to main content

rust_ef/query/builder/
filter.rs

1//! Filter, ordering, distinct, subquery, and pagination methods.
2
3use std::marker::PhantomData;
4
5use crate::entity::IEntityType;
6use crate::provider::DbValue;
7
8use super::super::ast::{
9    BoolExpr, FilterCondition, InSubquerySpec, OrderBy, OrderDirection, SubquerySpec,
10};
11use super::super::compile::{collect_bool_expr_values, filters_to_and_expr};
12use super::super::state::QueryState;
13use super::core::QueryBuilder;
14
15impl<T: IEntityType> QueryBuilder<T> {
16    #[doc(hidden)]
17    pub fn filter_column(
18        mut self,
19        column: &str,
20        operator: &str,
21        value: impl Into<DbValue>,
22    ) -> Self {
23        let db_val = value.into();
24        self.state.parameters.push(db_val);
25        self.state
26            .append_filter(FilterCondition::new(column, operator, 1));
27        self
28    }
29
30    #[doc(hidden)]
31    pub fn filter_not(mut self, column: &str, operator: &str, value: impl Into<DbValue>) -> Self {
32        let db_val = value.into();
33        self.state.parameters.push(db_val);
34        self.state
35            .append_bool_expr(BoolExpr::Not(Box::new(BoolExpr::Filter(
36                FilterCondition::new(column, operator, 1),
37            ))));
38        self
39    }
40
41    #[doc(hidden)]
42    pub fn filter_in(mut self, column: &str, values: Vec<DbValue>) -> Self {
43        let count = values.len();
44        for v in values {
45            self.state.parameters.push(v);
46        }
47        self.state
48            .append_filter(FilterCondition::new(column, "IN", count));
49        self
50    }
51
52    #[doc(hidden)]
53    pub fn filter_not_in(mut self, column: &str, values: Vec<DbValue>) -> Self {
54        let count = values.len();
55        for v in values {
56            self.state.parameters.push(v);
57        }
58        self.state
59            .append_bool_expr(BoolExpr::Not(Box::new(BoolExpr::Filter(
60                FilterCondition::new(column, "IN", count),
61            ))));
62        self
63    }
64
65    #[doc(hidden)]
66    pub fn filter_is_null(mut self, column: &str) -> Self {
67        self.state
68            .append_filter(FilterCondition::new(column, "IS NULL", 0));
69        self
70    }
71
72    #[doc(hidden)]
73    pub fn filter_is_not_null(mut self, column: &str) -> Self {
74        self.state
75            .append_filter(FilterCondition::new(column, "IS NOT NULL", 0));
76        self
77    }
78
79    #[doc(hidden)]
80    pub fn filter_between(
81        mut self,
82        column: &str,
83        low: impl Into<DbValue>,
84        high: impl Into<DbValue>,
85    ) -> Self {
86        let lo: DbValue = low.into();
87        let hi: DbValue = high.into();
88        self.state.parameters.push(lo);
89        self.state.parameters.push(hi);
90        self.state
91            .append_filter(FilterCondition::new(column, "BETWEEN", 2));
92        self
93    }
94
95    #[doc(hidden)]
96    pub fn filter_like(self, column: &str, pattern: impl Into<DbValue>) -> Self {
97        self.filter_column(column, "LIKE", pattern)
98    }
99
100    #[doc(hidden)]
101    pub fn filter_not_like(self, column: &str, pattern: impl Into<DbValue>) -> Self {
102        self.filter_not(column, "LIKE", pattern)
103    }
104
105    #[doc(hidden)]
106    pub fn order_by_column(mut self, column: &str) -> Self {
107        self.state
108            .orderings
109            .push(OrderBy::new(column, OrderDirection::Ascending));
110        self
111    }
112
113    #[doc(hidden)]
114    pub fn order_by_desc_column(mut self, column: &str) -> Self {
115        self.state
116            .orderings
117            .push(OrderBy::new(column, OrderDirection::Descending));
118        self
119    }
120
121    /// Marks this query as `SELECT DISTINCT`.
122    pub fn distinct(mut self) -> Self {
123        self.state.distinct = true;
124        self
125    }
126
127    #[doc(hidden)]
128    pub fn or_where(mut self, f: impl FnOnce(QueryBuilder<T>) -> QueryBuilder<T>) -> Self {
129        let sub = f(QueryBuilder {
130            state: QueryState::new(&self.state.from),
131            provider: self.provider.clone(),
132            filter_map: None,
133            lazy_loading_enabled: false,
134            _phantom: PhantomData,
135        });
136        let right = sub.state.where_expr.or_else(|| {
137            if sub.state.filters.is_empty() {
138                None
139            } else {
140                Some(filters_to_and_expr(&sub.state.filters))
141            }
142        });
143        if let Some(right_expr) = right {
144            self.state.where_expr = Some(match self.state.where_expr.take() {
145                None => right_expr,
146                Some(left) => BoolExpr::Or(Box::new(left), Box::new(right_expr)),
147            });
148            self.state.parameters.extend(sub.state.parameters);
149            self.state.filters.extend(sub.state.filters);
150        }
151        self
152    }
153
154    /// G5: Adds an `EXISTS` (or `NOT EXISTS`) correlated subquery condition.
155    ///
156    /// `#[doc(hidden)]` — called by `linq!` expansion of
157    /// `b.posts.any(|p| p.published)` / `b.posts.none(...)` / `b.posts.all(...)`.
158    ///
159    /// The `nav_field` and `related_type` arguments are the `&'static str`
160    /// constants emitted by `#[derive(EntityType)]` (`FIELD_<NAME>` and
161    /// `NAV_RELATED_<NAME>`). The table/column fields of the `SubquerySpec`
162    /// are resolved later at SQL generation time via `resolve_subqueries`.
163    pub fn where_exists_internal(
164        mut self,
165        nav_field: &'static str,
166        related_type: &'static str,
167        predicate: Option<BoolExpr>,
168        negated: bool,
169    ) -> Self {
170        let mut spec = SubquerySpec::new(nav_field, related_type);
171        if let Some(pred) = predicate {
172            let values = collect_bool_expr_values(&pred);
173            self.state.parameters.extend(values);
174            spec.predicate = Some(Box::new(pred));
175        }
176        let expr = if negated {
177            BoolExpr::NotExists(Box::new(spec))
178        } else {
179            BoolExpr::Exists(Box::new(spec))
180        };
181        self.state.append_bool_expr(expr);
182        self
183    }
184
185    /// v1.1: Adds an `IN (SELECT ...)` (or `NOT IN (SELECT ...)`) subquery
186    /// condition.
187    ///
188    /// `#[doc(hidden)]` — called by `linq!` expansion of
189    /// `b.field.in_subquery(|p: Post| p.blog_id)`.
190    ///
191    /// Unlike `where_exists_internal`, the `InSubquerySpec` is fully
192    /// specified at construction time (no navigation resolution needed).
193    /// The `source_table` and `projection_column` are `&'static str`
194    /// constants emitted by `#[derive(EntityType)]` (`TABLE` and
195    /// `COLUMN_<NAME>`).
196    pub fn where_in_subquery_internal(
197        mut self,
198        outer_column: &'static str,
199        source_table: &'static str,
200        projection_column: &'static str,
201        predicate: Option<BoolExpr>,
202        negated: bool,
203    ) -> Self {
204        let mut spec = InSubquerySpec::new(outer_column, source_table, projection_column);
205        if let Some(pred) = predicate {
206            let values = collect_bool_expr_values(&pred);
207            self.state.parameters.extend(values);
208            spec.predicate = Some(Box::new(pred));
209        }
210        let expr = if negated {
211            BoolExpr::NotInSubquery(Box::new(spec))
212        } else {
213            BoolExpr::InSubquery(Box::new(spec))
214        };
215        self.state.append_bool_expr(expr);
216        self
217    }
218
219    /// Skips the specified number of rows.
220    pub fn skip(mut self, count: usize) -> Self {
221        self.state.offset = Some(count);
222        self
223    }
224
225    /// Takes the specified number of rows.
226    pub fn take(mut self, count: usize) -> Self {
227        self.state.limit = Some(count);
228        self
229    }
230}