Skip to main content

rust_ef/query/
predicate.rs

1//! Composable query predicates — `AND` / `OR` / `NOT` over expression trees.
2//!
3//! `linq!(|b: T| ...)` produces a self-contained [`Predicate<T>`] that
4//! already carries its bound values, so predicates can be built condition by
5//! condition and combined before they are applied — the rust-ef equivalent of
6//! EF Core's expression-tree `PredicateBuilder`:
7//!
8//! - [`Predicate::always`] / [`Predicate::never`] seed an `AND` / `OR` chain,
9//! - [`Predicate::and`] / [`Predicate::or`] / [`Predicate::not`] nest freely,
10//!   with [`Predicate::and_opt`] / [`Predicate::or_opt`] for optional filters,
11//! - [`QueryBuilder::filter`] applies the result, appending it with `AND` and
12//!   registering its values in tree order.
13//!
14//! A predicate is a value: it can be stored, cloned, and reused across the
15//! `count()` / page / export queries of the same request, and combined before it
16//! is ever applied. [`BoolExpr`] is the encoded tree behind it; use
17//! [`Predicate::into_expr`] when an API asks for a bare expression.
18
19use std::marker::PhantomData;
20
21use crate::entity::IEntityType;
22
23use super::ast::BoolExpr;
24
25/// A composable WHERE predicate for entity type `T`.
26///
27/// `T` tags the predicate so that fragments written for different entities do
28/// not silently combine or get applied to the wrong query builder.
29///
30/// ```
31/// use rust_ef::prelude::*;
32/// use rust_ef::query::QueryBuilder;
33///
34/// #[derive(Debug, Clone, EntityType)]
35/// #[table("products")]
36/// struct Product {
37///     #[primary_key]
38///     id: i32,
39///     name: String,
40///     description: String,
41///     price: f64,
42///     is_deleted: bool,
43/// }
44///
45/// fn product_filter(keyword: Option<&str>, min_price: Option<f64>) -> Predicate<Product> {
46///     let keyword_group = keyword.map(|keyword| {
47///         linq!(|p: Product| p.name.contains(keyword))
48///             .or(linq!(|p: Product| p.description.contains(keyword)))
49///     });
50///     let min_price = min_price.map(|min_price| linq!(|p: Product| p.price >= min_price));
51///
52///     // always() 是 AND 链起点,本身不产生 SQL;
53///     // and_opt 让「可选条件」不需要额外 if let。
54///     Predicate::<Product>::always()
55///         .and(linq!(|p: Product| !p.is_deleted))
56///         .and_opt(keyword_group)
57///         .and_opt(min_price)
58/// }
59///
60/// let predicate = product_filter(Some("rust"), Some(9.9));
61/// let sql = QueryBuilder::<Product>::new("products")
62///     .filter(predicate)
63///     .to_sql();
64/// // NOT (is_deleted = ?) AND ((name LIKE ?) OR (description LIKE ?)) AND (price >= ?)
65/// assert!(sql.contains("(name LIKE ?) OR (description LIKE ?)"));
66/// ```
67#[derive(Debug, Clone)]
68pub struct Predicate<T: IEntityType> {
69    kind: PredicateKind,
70    _marker: PhantomData<fn() -> T>,
71}
72
73#[derive(Debug, Clone)]
74enum PredicateKind {
75    /// Matches every row (`1=1`) — the identity of `AND`.
76    Always,
77    /// Matches no row (`1=0`) — the identity of `OR`.
78    Never,
79    /// A self-contained expression tree, e.g. from `linq!(|b: T| ...)`.
80    Expr(BoolExpr),
81}
82
83impl<T: IEntityType> Predicate<T> {
84    /// A predicate that matches every row — the seed of an `AND` chain.
85    ///
86    /// `Predicate::always().and(p)` is `p`, so seeding with it adds no SQL.
87    pub fn always() -> Self {
88        Self::with_kind(PredicateKind::Always)
89    }
90
91    /// A predicate that matches no row — the seed of an `OR` chain.
92    ///
93    /// `Predicate::never().or(p)` is `p`, so seeding with it adds no SQL.
94    pub fn never() -> Self {
95        Self::with_kind(PredicateKind::Never)
96    }
97
98    /// Wraps an expression tree as a predicate.
99    ///
100    /// Prefer `linq!(|b: T| ...)`, which is already typed and
101    /// self-contained. This is the unchecked door for reusing a `BoolExpr`
102    /// produced elsewhere (e.g. a filter read back from `ModelBuilder`).
103    pub fn from_expr(expr: BoolExpr) -> Self {
104        Self::with_kind(PredicateKind::Expr(expr))
105    }
106
107    fn with_kind(kind: PredicateKind) -> Self {
108        Self {
109            kind,
110            _marker: PhantomData,
111        }
112    }
113
114    /// Whether the predicate matches every row (the `AND` identity).
115    pub fn is_always(&self) -> bool {
116        matches!(self.kind, PredicateKind::Always)
117    }
118
119    /// Whether the predicate matches no row.
120    pub fn is_never(&self) -> bool {
121        matches!(self.kind, PredicateKind::Never)
122    }
123
124    /// `self AND other`.
125    ///
126    /// `always()` is the identity (`always().and(p) == p`) and `never()` is
127    /// absorbing (`never().and(p) == never()`), so identities never leak into
128    /// the generated SQL.
129    pub fn and(self, other: impl Into<Predicate<T>>) -> Self {
130        let rhs = other.into().kind;
131        let kind = match (self.kind, rhs) {
132            (PredicateKind::Never, _) | (_, PredicateKind::Never) => PredicateKind::Never,
133            (PredicateKind::Always, rhs) => rhs,
134            (lhs, PredicateKind::Always) => lhs,
135            (PredicateKind::Expr(lhs), PredicateKind::Expr(rhs)) => {
136                PredicateKind::Expr(BoolExpr::And(Box::new(lhs), Box::new(rhs)))
137            }
138        };
139        Self::with_kind(kind)
140    }
141
142    /// `self AND other` when `other` is `Some` — the one-liner for an optional
143    /// filter. `None` leaves the predicate unchanged.
144    pub fn and_opt(self, other: Option<impl Into<Predicate<T>>>) -> Self {
145        match other {
146            Some(other) => self.and(other),
147            None => self,
148        }
149    }
150
151    /// `self OR other`.
152    ///
153    /// `never()` is the identity (`never().or(p) == p`) and `always()` is
154    /// absorbing (`always().or(p) == always()`).
155    pub fn or(self, other: impl Into<Predicate<T>>) -> Self {
156        let rhs = other.into().kind;
157        let kind = match (self.kind, rhs) {
158            (PredicateKind::Always, _) | (_, PredicateKind::Always) => PredicateKind::Always,
159            (PredicateKind::Never, rhs) => rhs,
160            (lhs, PredicateKind::Never) => lhs,
161            (PredicateKind::Expr(lhs), PredicateKind::Expr(rhs)) => {
162                PredicateKind::Expr(BoolExpr::Or(Box::new(lhs), Box::new(rhs)))
163            }
164        };
165        Self::with_kind(kind)
166    }
167
168    /// `self OR other` when `other` is `Some`; `None` leaves the predicate
169    /// unchanged.
170    pub fn or_opt(self, other: Option<impl Into<Predicate<T>>>) -> Self {
171        match other {
172            Some(other) => self.or(other),
173            None => self,
174        }
175    }
176
177    /// `NOT self`.
178    #[allow(clippy::should_implement_trait)]
179    pub fn not(self) -> Self {
180        let kind = match self.kind {
181            PredicateKind::Always => PredicateKind::Never,
182            PredicateKind::Never => PredicateKind::Always,
183            PredicateKind::Expr(expr) => PredicateKind::Expr(BoolExpr::Not(Box::new(expr))),
184        };
185        Self::with_kind(kind)
186    }
187
188    /// Converts the predicate to a [`BoolExpr`] — for APIs that take an
189    /// expression directly, such as `ModelBuilder::has_query_filter`.
190    ///
191    /// `always()` and `never()` become the literal fragments `1=1` / `1=0`.
192    pub fn into_expr(self) -> BoolExpr {
193        match self.kind {
194            PredicateKind::Always => BoolExpr::raw("1=1"),
195            PredicateKind::Never => BoolExpr::raw("1=0"),
196            PredicateKind::Expr(expr) => expr,
197        }
198    }
199
200    /// WHERE-clause view: `None` for the `AND` identity (`always()`), which
201    /// needs no SQL at all.
202    pub(crate) fn into_where_expr(self) -> Option<BoolExpr> {
203        match self.kind {
204            PredicateKind::Always => None,
205            PredicateKind::Never => Some(BoolExpr::raw("1=0")),
206            PredicateKind::Expr(expr) => Some(expr),
207        }
208    }
209}
210
211impl<T: IEntityType> From<BoolExpr> for Predicate<T> {
212    /// Lets `BoolExpr` fragments (e.g. a stored global filter) be combined
213    /// directly. This conversion is unchecked: prefer `linq!(...)`.
214    fn from(expr: BoolExpr) -> Self {
215        Self::from_expr(expr)
216    }
217}
218
219impl<T: IEntityType> From<Predicate<T>> for BoolExpr {
220    fn from(predicate: Predicate<T>) -> Self {
221        predicate.into_expr()
222    }
223}