Skip to main content

vantage_sql/
condition.rs

1//! Backend-specific condition wrappers and operation traits.
2//!
3//! **Conditions:** Each wrapper (e.g., `SqliteCondition`) is a newtype around
4//! `Expression<BackendType>`. It accepts `Expression<F>` for any `F: Into<BackendType>`,
5//! plus common types (`Identifier`, `Fx`) via `From`.
6//!
7//! **Operations:** Each backend gets a vendor-specific operation trait (e.g.
8//! `SqliteOperation<T>`) that produces the backend's condition type directly.
9//! These are blanket-implemented for all `Expressive<T>` where `T: Into<AnyType>`,
10//! and the condition type implements `Expressive<AnyType>` to enable chaining:
11//!
12//! ```ignore
13//! use vantage_sql::sqlite::operation::SqliteOperation;
14//! let price = Column::<i64>::new("price");
15//! price.gt(10).eq(false)  // => SqliteCondition wrapping (price > 10) = 0
16//! ```
17
18use vantage_expressions::traits::expressive::ExpressiveEnum;
19use vantage_expressions::{Expression, Expressive};
20
21use crate::primitives::fx::Fx;
22use crate::primitives::identifier::Identifier;
23
24macro_rules! define_sql_condition {
25    ($name:ident, $any_type:ty) => {
26        /// Condition wrapper that preserves type inference for `with_condition()`.
27        #[derive(Debug, Clone)]
28        pub struct $name(pub Expression<$any_type>);
29
30        impl $name {
31            pub fn into_expr(self) -> Expression<$any_type> {
32                self.0
33            }
34
35            /// Create from a typed expression by mapping scalars via `Into<BackendType>`.
36            ///
37            /// Used by the generic `From<Expression<F>>` impl.
38            pub fn from_typed<F>(expr: Expression<F>) -> Self
39            where
40                F: Into<$any_type> + Send + Clone + 'static,
41            {
42                use vantage_expressions::ExpressionMap;
43                Self(expr.map())
44            }
45        }
46
47        impl<F> From<Expression<F>> for $name
48        where
49            F: Into<$any_type> + Send + Clone + 'static,
50        {
51            fn from(expr: Expression<F>) -> Self {
52                Self::from_typed(expr)
53            }
54        }
55
56        impl From<Identifier> for $name {
57            fn from(id: Identifier) -> Self {
58                use vantage_expressions::Expressive;
59                Self(id.expr())
60            }
61        }
62
63        impl From<$name> for Expression<$any_type> {
64            fn from(cond: $name) -> Self {
65                cond.0
66            }
67        }
68
69        impl From<Fx<$any_type>> for $name {
70            fn from(fx: Fx<$any_type>) -> Self {
71                Self(fx.into())
72            }
73        }
74    };
75}
76
77#[cfg(feature = "sqlite")]
78define_sql_condition!(SqliteCondition, crate::sqlite::types::AnySqliteType);
79
80#[cfg(feature = "postgres")]
81define_sql_condition!(PostgresCondition, crate::postgres::types::AnyPostgresType);
82
83#[cfg(feature = "mysql")]
84define_sql_condition!(MysqlCondition, crate::mysql::types::AnyMysqlType);
85
86#[cfg(feature = "mysql")]
87impl From<crate::mysql::statements::primitives::FulltextMatch> for MysqlCondition {
88    fn from(fm: crate::mysql::statements::primitives::FulltextMatch) -> Self {
89        Self(fm.into())
90    }
91}
92
93/// `column IN (values)`, in the shape `condition_column_of` reads. An empty
94/// list renders `IN (NULL)`: `IN ()` isn't SQL on every dialect, and
95/// `x IN (NULL)` matches no row.
96pub(crate) fn in_values<V, C>(column: &impl Expressive<V>, values: &[V]) -> C
97where
98    V: Clone + Send + Sync + 'static,
99    C: From<Expression<V>>,
100{
101    let list = if values.is_empty() {
102        Expression::new("NULL", vec![])
103    } else {
104        let items = values
105            .iter()
106            .map(|v| Expression::new("{}", vec![ExpressiveEnum::Scalar(v.clone())]))
107            .collect();
108        Expression::from_vec(items, ", ")
109    };
110    Expression::new(
111        "{} IN ({})",
112        vec![
113            ExpressiveEnum::Nested(column.expr()),
114            ExpressiveEnum::Nested(list),
115        ],
116    )
117    .into()
118}
119
120/// Defines a backend-specific identifier wrapper that only implements
121/// `Expressive<$any_type>`, avoiding ambiguity when multiple backend
122/// features are enabled.
123///
124/// Usage: `define_typed_ident!(PgIdent, pg_ident, AnyPostgresType, PostgresCondition);`
125#[macro_export]
126macro_rules! define_typed_ident {
127    ($struct_name:ident, $fn_name:ident, $any_type:ty, $condition:ty) => {
128        #[derive(Debug, Clone)]
129        pub struct $struct_name($crate::primitives::identifier::Identifier);
130
131        impl $struct_name {
132            pub fn new(name: impl Into<String>) -> Self {
133                Self($crate::primitives::identifier::ident(name))
134            }
135
136            pub fn dot_of(mut self, prefix: impl Into<String>) -> Self {
137                self.0 = self.0.dot_of(prefix);
138                self
139            }
140
141            pub fn with_alias(mut self, alias: impl Into<String>) -> Self {
142                self.0 = self.0.with_alias(alias);
143                self
144            }
145
146            pub fn name(&self) -> String {
147                self.0.name()
148            }
149
150            pub fn alias(&self) -> Option<&str> {
151                self.0.alias()
152            }
153        }
154
155        impl $crate::vantage_expressions::Expressive<$any_type> for $struct_name {
156            fn expr(&self) -> $crate::vantage_expressions::Expression<$any_type> {
157                $crate::vantage_expressions::Expressive::<$any_type>::expr(&self.0)
158            }
159        }
160
161        impl From<$struct_name> for $crate::vantage_expressions::Expression<$any_type> {
162            fn from(id: $struct_name) -> Self {
163                $crate::vantage_expressions::Expressive::<$any_type>::expr(&id.0)
164            }
165        }
166
167        impl From<$struct_name> for $condition {
168            fn from(id: $struct_name) -> Self {
169                Self::from_typed($crate::vantage_expressions::Expressive::<$any_type>::expr(
170                    &id.0,
171                ))
172            }
173        }
174
175        /// Shorthand constructor.
176        pub fn $fn_name(name: impl Into<String>) -> $struct_name {
177            $struct_name::new(name)
178        }
179    };
180}
181
182#[macro_export]
183macro_rules! define_sql_operation {
184    ($trait_name:ident, $condition:ident, $any_type:ty) => {
185        /// Vendor-specific operations producing the backend's condition type.
186        ///
187        /// Blanket-implemented for all `Expressive<T>` where `T: Into<AnyType>`.
188        /// The condition type itself implements `Expressive<AnyType>`, enabling
189        /// cross-type chaining like `price.gt(10).eq(false)`.
190        pub trait $trait_name<T>: $crate::vantage_expressions::Expressive<T>
191        where
192            T: Into<$any_type> + Send + Clone + 'static,
193        {
194            /// `(self OR other)` — joins two conditions into a
195            /// `ConditionGroup`.
196            ///
197            /// Use this method to write alternatives. Do not write
198            /// `"a OR b"` as text. The group writes its own brackets,
199            /// and it keeps its meaning next to the other conditions.
200            /// Text has no brackets, and `AND` binds more tightly than
201            /// `OR`. Thus `role = 'admin' AND a OR b` means
202            /// `(role = 'admin' AND a) OR b`.
203            ///
204            /// A chain stays flat: `a.or_(b).or_(c)` gives
205            /// `(a OR b OR c)`.
206            fn or_(
207                &self,
208                other: impl $crate::vantage_expressions::Expressive<T>,
209            ) -> $crate::primitives::ConditionGroup<T>
210            where
211                Self: Sized,
212            {
213                $crate::primitives::or_(self.expr(), other.expr())
214            }
215
216            /// `(self AND other)` — joins two conditions into a
217            /// `ConditionGroup`.
218            ///
219            /// A table joins its conditions with `AND` already. Use this
220            /// method when you must make a group inside an `or_`.
221            fn and_(
222                &self,
223                other: impl $crate::vantage_expressions::Expressive<T>,
224            ) -> $crate::primitives::ConditionGroup<T>
225            where
226                Self: Sized,
227            {
228                $crate::primitives::and_(self.expr(), other.expr())
229            }
230
231            /// `field = value`
232            fn eq(&self, value: impl $crate::vantage_expressions::Expressive<T>) -> $condition
233            where
234                Self: Sized,
235            {
236                $crate::condition::build_sql_binary::<T, $any_type, $condition>(
237                    self, value, "{} = {}",
238                )
239            }
240
241            /// `field != value`
242            fn ne(&self, value: impl $crate::vantage_expressions::Expressive<T>) -> $condition
243            where
244                Self: Sized,
245            {
246                $crate::condition::build_sql_binary::<T, $any_type, $condition>(
247                    self, value, "{} != {}",
248                )
249            }
250
251            /// `field > value`
252            fn gt(&self, value: impl $crate::vantage_expressions::Expressive<T>) -> $condition
253            where
254                Self: Sized,
255            {
256                $crate::condition::build_sql_binary::<T, $any_type, $condition>(
257                    self, value, "{} > {}",
258                )
259            }
260
261            /// `field >= value`
262            fn gte(&self, value: impl $crate::vantage_expressions::Expressive<T>) -> $condition
263            where
264                Self: Sized,
265            {
266                $crate::condition::build_sql_binary::<T, $any_type, $condition>(
267                    self, value, "{} >= {}",
268                )
269            }
270
271            /// `field < value`
272            fn lt(&self, value: impl $crate::vantage_expressions::Expressive<T>) -> $condition
273            where
274                Self: Sized,
275            {
276                $crate::condition::build_sql_binary::<T, $any_type, $condition>(
277                    self, value, "{} < {}",
278                )
279            }
280
281            /// `field <= value`
282            fn lte(&self, value: impl $crate::vantage_expressions::Expressive<T>) -> $condition
283            where
284                Self: Sized,
285            {
286                $crate::condition::build_sql_binary::<T, $any_type, $condition>(
287                    self, value, "{} <= {}",
288                )
289            }
290
291            /// `field IN (values_expression)`
292            fn in_(&self, values: impl $crate::vantage_expressions::Expressive<T>) -> $condition
293            where
294                Self: Sized,
295            {
296                $crate::condition::build_sql_binary::<T, $any_type, $condition>(
297                    self,
298                    values,
299                    "{} IN ({})",
300                )
301            }
302
303            /// `field IN (a, b, c)` from a slice of scalar values
304            fn in_list<V: Into<T> + Clone>(&self, values: &[V]) -> $condition
305            where
306                Self: Sized,
307                T: Clone,
308            {
309                use $crate::vantage_expressions::Expression;
310                use $crate::vantage_expressions::traits::expressive::ExpressiveEnum;
311                let params: Vec<Expression<T>> = values
312                    .iter()
313                    .map(|v| Expression::new("{}", vec![ExpressiveEnum::Scalar(v.clone().into())]))
314                    .collect();
315                let expr: Expression<T> = Expression::new(
316                    "{} IN ({})",
317                    vec![
318                        ExpressiveEnum::Nested(self.expr()),
319                        ExpressiveEnum::Nested(Expression::from_vec(params, ", ")),
320                    ],
321                );
322                $condition::from_typed(expr)
323            }
324
325            /// `field NOT IN (values_expression)`
326            fn not_in(&self, values: impl $crate::vantage_expressions::Expressive<T>) -> $condition
327            where
328                Self: Sized,
329            {
330                $crate::condition::build_sql_binary::<T, $any_type, $condition>(
331                    self,
332                    values,
333                    "{} NOT IN ({})",
334                )
335            }
336
337            /// `field NOT IN (a, b, c)` from a slice of scalar values
338            fn not_in_list<V: Into<T> + Clone>(&self, values: &[V]) -> $condition
339            where
340                Self: Sized,
341                T: Clone,
342            {
343                use $crate::vantage_expressions::Expression;
344                use $crate::vantage_expressions::traits::expressive::ExpressiveEnum;
345                let params: Vec<Expression<T>> = values
346                    .iter()
347                    .map(|v| Expression::new("{}", vec![ExpressiveEnum::Scalar(v.clone().into())]))
348                    .collect();
349                let expr: Expression<T> = Expression::new(
350                    "{} NOT IN ({})",
351                    vec![
352                        ExpressiveEnum::Nested(self.expr()),
353                        ExpressiveEnum::Nested(Expression::from_vec(params, ", ")),
354                    ],
355                );
356                $condition::from_typed(expr)
357            }
358
359            /// `CAST(expr AS type_name)`
360            fn cast(&self, type_name: &str) -> $condition
361            where
362                Self: Sized,
363            {
364                use $crate::vantage_expressions::Expression;
365                use $crate::vantage_expressions::traits::expressive::ExpressiveEnum;
366                let expr: Expression<T> = Expression::new(
367                    format!("CAST({{}} AS {type_name})"),
368                    vec![ExpressiveEnum::Nested(self.expr())],
369                );
370                $condition::from_typed(expr)
371            }
372
373            /// `field IS NULL`
374            fn is_null(&self) -> $condition
375            where
376                Self: Sized,
377            {
378                use $crate::vantage_expressions::Expression;
379                use $crate::vantage_expressions::traits::expressive::ExpressiveEnum;
380                let expr: Expression<T> =
381                    Expression::new("{} IS NULL", vec![ExpressiveEnum::Nested(self.expr())]);
382                $condition::from_typed(expr)
383            }
384
385            /// `field IS NOT NULL`
386            fn is_not_null(&self) -> $condition
387            where
388                Self: Sized,
389            {
390                use $crate::vantage_expressions::Expression;
391                use $crate::vantage_expressions::traits::expressive::ExpressiveEnum;
392                let expr: Expression<T> =
393                    Expression::new("{} IS NOT NULL", vec![ExpressiveEnum::Nested(self.expr())]);
394                $condition::from_typed(expr)
395            }
396        }
397
398        /// Blanket: any `Expressive<T>` where `T: Into<AnyType>` gets the
399        /// operation trait for free.
400        impl<T, S> $trait_name<T> for S
401        where
402            S: $crate::vantage_expressions::Expressive<T>,
403            T: Into<$any_type> + Send + Clone + 'static,
404        {
405        }
406
407        /// Condition chaining: the condition type wraps `Expression<AnyType>`,
408        /// so implementing `Expressive<AnyType>` gives it the operation trait
409        /// via the blanket above.
410        impl $crate::vantage_expressions::Expressive<$any_type> for $condition {
411            fn expr(&self) -> $crate::vantage_expressions::Expression<$any_type> {
412                self.0.clone()
413            }
414        }
415    };
416}
417
418/// Helper for `define_sql_operation!`: build a binary expression, map to
419/// the backend's condition type. Public so the macro can call it from
420/// any module.
421pub fn build_sql_binary<T, AnyType, Cond>(
422    lhs: &(impl Expressive<T> + ?Sized),
423    rhs: impl Expressive<T>,
424    template: &str,
425) -> Cond
426where
427    T: Into<AnyType> + Send + Clone + 'static,
428    Cond: From<Expression<T>>,
429{
430    let expr: Expression<T> = Expression::new(
431        template,
432        vec![
433            ExpressiveEnum::Nested(lhs.expr()),
434            ExpressiveEnum::Nested(rhs.expr()),
435        ],
436    );
437    Cond::from(expr)
438}