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/// Defines a backend-specific identifier wrapper that only implements
94/// `Expressive<$any_type>`, avoiding ambiguity when multiple backend
95/// features are enabled.
96///
97/// Usage: `define_typed_ident!(PgIdent, pg_ident, AnyPostgresType, PostgresCondition);`
98#[macro_export]
99macro_rules! define_typed_ident {
100    ($struct_name:ident, $fn_name:ident, $any_type:ty, $condition:ty) => {
101        #[derive(Debug, Clone)]
102        pub struct $struct_name($crate::primitives::identifier::Identifier);
103
104        impl $struct_name {
105            pub fn new(name: impl Into<String>) -> Self {
106                Self($crate::primitives::identifier::ident(name))
107            }
108
109            pub fn dot_of(mut self, prefix: impl Into<String>) -> Self {
110                self.0 = self.0.dot_of(prefix);
111                self
112            }
113
114            pub fn with_alias(mut self, alias: impl Into<String>) -> Self {
115                self.0 = self.0.with_alias(alias);
116                self
117            }
118
119            pub fn name(&self) -> String {
120                self.0.name()
121            }
122
123            pub fn alias(&self) -> Option<&str> {
124                self.0.alias()
125            }
126        }
127
128        impl $crate::vantage_expressions::Expressive<$any_type> for $struct_name {
129            fn expr(&self) -> $crate::vantage_expressions::Expression<$any_type> {
130                $crate::vantage_expressions::Expressive::<$any_type>::expr(&self.0)
131            }
132        }
133
134        impl From<$struct_name> for $crate::vantage_expressions::Expression<$any_type> {
135            fn from(id: $struct_name) -> Self {
136                $crate::vantage_expressions::Expressive::<$any_type>::expr(&id.0)
137            }
138        }
139
140        impl From<$struct_name> for $condition {
141            fn from(id: $struct_name) -> Self {
142                Self::from_typed($crate::vantage_expressions::Expressive::<$any_type>::expr(
143                    &id.0,
144                ))
145            }
146        }
147
148        /// Shorthand constructor.
149        pub fn $fn_name(name: impl Into<String>) -> $struct_name {
150            $struct_name::new(name)
151        }
152    };
153}
154
155#[macro_export]
156macro_rules! define_sql_operation {
157    ($trait_name:ident, $condition:ident, $any_type:ty) => {
158        /// Vendor-specific operations producing the backend's condition type.
159        ///
160        /// Blanket-implemented for all `Expressive<T>` where `T: Into<AnyType>`.
161        /// The condition type itself implements `Expressive<AnyType>`, enabling
162        /// cross-type chaining like `price.gt(10).eq(false)`.
163        pub trait $trait_name<T>: $crate::vantage_expressions::Expressive<T>
164        where
165            T: Into<$any_type> + Send + Clone + 'static,
166        {
167            /// `(self OR other)` — joins two conditions into a
168            /// `ConditionGroup`.
169            ///
170            /// Use this method to write alternatives. Do not write
171            /// `"a OR b"` as text. The group writes its own brackets,
172            /// and it keeps its meaning next to the other conditions.
173            /// Text has no brackets, and `AND` binds more tightly than
174            /// `OR`. Thus `role = 'admin' AND a OR b` means
175            /// `(role = 'admin' AND a) OR b`.
176            ///
177            /// A chain stays flat: `a.or_(b).or_(c)` gives
178            /// `(a OR b OR c)`.
179            fn or_(
180                &self,
181                other: impl $crate::vantage_expressions::Expressive<T>,
182            ) -> $crate::primitives::ConditionGroup<T>
183            where
184                Self: Sized,
185            {
186                $crate::primitives::or_(self.expr(), other.expr())
187            }
188
189            /// `(self AND other)` — joins two conditions into a
190            /// `ConditionGroup`.
191            ///
192            /// A table joins its conditions with `AND` already. Use this
193            /// method when you must make a group inside an `or_`.
194            fn and_(
195                &self,
196                other: impl $crate::vantage_expressions::Expressive<T>,
197            ) -> $crate::primitives::ConditionGroup<T>
198            where
199                Self: Sized,
200            {
201                $crate::primitives::and_(self.expr(), other.expr())
202            }
203
204            /// `field = value`
205            fn eq(&self, value: impl $crate::vantage_expressions::Expressive<T>) -> $condition
206            where
207                Self: Sized,
208            {
209                $crate::condition::build_sql_binary::<T, $any_type, $condition>(
210                    self, value, "{} = {}",
211                )
212            }
213
214            /// `field != value`
215            fn ne(&self, value: impl $crate::vantage_expressions::Expressive<T>) -> $condition
216            where
217                Self: Sized,
218            {
219                $crate::condition::build_sql_binary::<T, $any_type, $condition>(
220                    self, value, "{} != {}",
221                )
222            }
223
224            /// `field > value`
225            fn gt(&self, value: impl $crate::vantage_expressions::Expressive<T>) -> $condition
226            where
227                Self: Sized,
228            {
229                $crate::condition::build_sql_binary::<T, $any_type, $condition>(
230                    self, value, "{} > {}",
231                )
232            }
233
234            /// `field >= value`
235            fn gte(&self, value: impl $crate::vantage_expressions::Expressive<T>) -> $condition
236            where
237                Self: Sized,
238            {
239                $crate::condition::build_sql_binary::<T, $any_type, $condition>(
240                    self, value, "{} >= {}",
241                )
242            }
243
244            /// `field < value`
245            fn lt(&self, value: impl $crate::vantage_expressions::Expressive<T>) -> $condition
246            where
247                Self: Sized,
248            {
249                $crate::condition::build_sql_binary::<T, $any_type, $condition>(
250                    self, value, "{} < {}",
251                )
252            }
253
254            /// `field <= value`
255            fn lte(&self, value: impl $crate::vantage_expressions::Expressive<T>) -> $condition
256            where
257                Self: Sized,
258            {
259                $crate::condition::build_sql_binary::<T, $any_type, $condition>(
260                    self, value, "{} <= {}",
261                )
262            }
263
264            /// `field IN (values_expression)`
265            fn in_(&self, values: impl $crate::vantage_expressions::Expressive<T>) -> $condition
266            where
267                Self: Sized,
268            {
269                $crate::condition::build_sql_binary::<T, $any_type, $condition>(
270                    self,
271                    values,
272                    "{} IN ({})",
273                )
274            }
275
276            /// `field IN (a, b, c)` from a slice of scalar values
277            fn in_list<V: Into<T> + Clone>(&self, values: &[V]) -> $condition
278            where
279                Self: Sized,
280                T: Clone,
281            {
282                use $crate::vantage_expressions::Expression;
283                use $crate::vantage_expressions::traits::expressive::ExpressiveEnum;
284                let params: Vec<Expression<T>> = values
285                    .iter()
286                    .map(|v| Expression::new("{}", vec![ExpressiveEnum::Scalar(v.clone().into())]))
287                    .collect();
288                let expr: Expression<T> = Expression::new(
289                    "{} IN ({})",
290                    vec![
291                        ExpressiveEnum::Nested(self.expr()),
292                        ExpressiveEnum::Nested(Expression::from_vec(params, ", ")),
293                    ],
294                );
295                $condition::from_typed(expr)
296            }
297
298            /// `field NOT IN (values_expression)`
299            fn not_in(&self, values: impl $crate::vantage_expressions::Expressive<T>) -> $condition
300            where
301                Self: Sized,
302            {
303                $crate::condition::build_sql_binary::<T, $any_type, $condition>(
304                    self,
305                    values,
306                    "{} NOT IN ({})",
307                )
308            }
309
310            /// `field NOT IN (a, b, c)` from a slice of scalar values
311            fn not_in_list<V: Into<T> + Clone>(&self, values: &[V]) -> $condition
312            where
313                Self: Sized,
314                T: Clone,
315            {
316                use $crate::vantage_expressions::Expression;
317                use $crate::vantage_expressions::traits::expressive::ExpressiveEnum;
318                let params: Vec<Expression<T>> = values
319                    .iter()
320                    .map(|v| Expression::new("{}", vec![ExpressiveEnum::Scalar(v.clone().into())]))
321                    .collect();
322                let expr: Expression<T> = Expression::new(
323                    "{} NOT IN ({})",
324                    vec![
325                        ExpressiveEnum::Nested(self.expr()),
326                        ExpressiveEnum::Nested(Expression::from_vec(params, ", ")),
327                    ],
328                );
329                $condition::from_typed(expr)
330            }
331
332            /// `CAST(expr AS type_name)`
333            fn cast(&self, type_name: &str) -> $condition
334            where
335                Self: Sized,
336            {
337                use $crate::vantage_expressions::Expression;
338                use $crate::vantage_expressions::traits::expressive::ExpressiveEnum;
339                let expr: Expression<T> = Expression::new(
340                    format!("CAST({{}} AS {type_name})"),
341                    vec![ExpressiveEnum::Nested(self.expr())],
342                );
343                $condition::from_typed(expr)
344            }
345
346            /// `field IS NULL`
347            fn is_null(&self) -> $condition
348            where
349                Self: Sized,
350            {
351                use $crate::vantage_expressions::Expression;
352                use $crate::vantage_expressions::traits::expressive::ExpressiveEnum;
353                let expr: Expression<T> =
354                    Expression::new("{} IS NULL", vec![ExpressiveEnum::Nested(self.expr())]);
355                $condition::from_typed(expr)
356            }
357
358            /// `field IS NOT NULL`
359            fn is_not_null(&self) -> $condition
360            where
361                Self: Sized,
362            {
363                use $crate::vantage_expressions::Expression;
364                use $crate::vantage_expressions::traits::expressive::ExpressiveEnum;
365                let expr: Expression<T> =
366                    Expression::new("{} IS NOT NULL", vec![ExpressiveEnum::Nested(self.expr())]);
367                $condition::from_typed(expr)
368            }
369        }
370
371        /// Blanket: any `Expressive<T>` where `T: Into<AnyType>` gets the
372        /// operation trait for free.
373        impl<T, S> $trait_name<T> for S
374        where
375            S: $crate::vantage_expressions::Expressive<T>,
376            T: Into<$any_type> + Send + Clone + 'static,
377        {
378        }
379
380        /// Condition chaining: the condition type wraps `Expression<AnyType>`,
381        /// so implementing `Expressive<AnyType>` gives it the operation trait
382        /// via the blanket above.
383        impl $crate::vantage_expressions::Expressive<$any_type> for $condition {
384            fn expr(&self) -> $crate::vantage_expressions::Expression<$any_type> {
385                self.0.clone()
386            }
387        }
388    };
389}
390
391/// Helper for `define_sql_operation!`: build a binary expression, map to
392/// the backend's condition type. Public so the macro can call it from
393/// any module.
394pub fn build_sql_binary<T, AnyType, Cond>(
395    lhs: &(impl Expressive<T> + ?Sized),
396    rhs: impl Expressive<T>,
397    template: &str,
398) -> Cond
399where
400    T: Into<AnyType> + Send + Clone + 'static,
401    Cond: From<Expression<T>>,
402{
403    let expr: Expression<T> = Expression::new(
404        template,
405        vec![
406            ExpressiveEnum::Nested(lhs.expr()),
407            ExpressiveEnum::Nested(rhs.expr()),
408        ],
409    );
410    Cond::from(expr)
411}