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        // From Expression<F> where F: Into<BackendType> — accepts both
48        // Expression<BackendType> (identity) and typed Expression<i64> etc.
49        impl<F> From<Expression<F>> for $name
50        where
51            F: Into<$any_type> + Send + Clone + 'static,
52        {
53            fn from(expr: Expression<F>) -> Self {
54                Self::from_typed(expr)
55            }
56        }
57
58        // From Identifier
59        impl From<Identifier> for $name {
60            fn from(id: Identifier) -> Self {
61                use vantage_expressions::Expressive;
62                Self(id.expr())
63            }
64        }
65
66        // Into Expression<BackendType> — unwrap the newtype
67        impl From<$name> for Expression<$any_type> {
68            fn from(cond: $name) -> Self {
69                cond.0
70            }
71        }
72
73        // From Fx<BackendType>
74        impl From<Fx<$any_type>> for $name {
75            fn from(fx: Fx<$any_type>) -> Self {
76                Self(fx.into())
77            }
78        }
79    };
80}
81
82#[cfg(feature = "sqlite")]
83define_sql_condition!(SqliteCondition, crate::sqlite::types::AnySqliteType);
84
85#[cfg(feature = "postgres")]
86define_sql_condition!(PostgresCondition, crate::postgres::types::AnyPostgresType);
87
88#[cfg(feature = "mysql")]
89define_sql_condition!(MysqlCondition, crate::mysql::types::AnyMysqlType);
90
91// MySQL-specific: FulltextMatch
92#[cfg(feature = "mysql")]
93impl From<crate::mysql::statements::primitives::FulltextMatch> for MysqlCondition {
94    fn from(fm: crate::mysql::statements::primitives::FulltextMatch) -> Self {
95        Self(fm.into())
96    }
97}
98
99// ── Backend-typed identifier wrapper ────────────────────────────────
100
101/// Defines a backend-specific identifier wrapper that only implements
102/// `Expressive<$any_type>`, avoiding ambiguity when multiple backend
103/// features are enabled.
104///
105/// Usage: `define_typed_ident!(PgIdent, pg_ident, AnyPostgresType, PostgresCondition);`
106#[macro_export]
107macro_rules! define_typed_ident {
108    ($struct_name:ident, $fn_name:ident, $any_type:ty, $condition:ty) => {
109        #[derive(Debug, Clone)]
110        pub struct $struct_name($crate::primitives::identifier::Identifier);
111
112        impl $struct_name {
113            pub fn new(name: impl Into<String>) -> Self {
114                Self($crate::primitives::identifier::ident(name))
115            }
116
117            pub fn dot_of(mut self, prefix: impl Into<String>) -> Self {
118                self.0 = self.0.dot_of(prefix);
119                self
120            }
121
122            pub fn with_alias(mut self, alias: impl Into<String>) -> Self {
123                self.0 = self.0.with_alias(alias);
124                self
125            }
126
127            pub fn name(&self) -> String {
128                self.0.name()
129            }
130
131            pub fn alias(&self) -> Option<&str> {
132                self.0.alias()
133            }
134        }
135
136        impl $crate::vantage_expressions::Expressive<$any_type> for $struct_name {
137            fn expr(&self) -> $crate::vantage_expressions::Expression<$any_type> {
138                $crate::vantage_expressions::Expressive::<$any_type>::expr(&self.0)
139            }
140        }
141
142        impl From<$struct_name> for $crate::vantage_expressions::Expression<$any_type> {
143            fn from(id: $struct_name) -> Self {
144                $crate::vantage_expressions::Expressive::<$any_type>::expr(&id.0)
145            }
146        }
147
148        impl From<$struct_name> for $condition {
149            fn from(id: $struct_name) -> Self {
150                Self::from_typed($crate::vantage_expressions::Expressive::<$any_type>::expr(
151                    &id.0,
152                ))
153            }
154        }
155
156        /// Shorthand constructor.
157        pub fn $fn_name(name: impl Into<String>) -> $struct_name {
158            $struct_name::new(name)
159        }
160    };
161}
162
163// ── Vendor-specific operation traits ─────────────────────────────────
164
165#[macro_export]
166macro_rules! define_sql_operation {
167    ($trait_name:ident, $condition:ident, $any_type:ty) => {
168        /// Vendor-specific operations producing the backend's condition type.
169        ///
170        /// Blanket-implemented for all `Expressive<T>` where `T: Into<AnyType>`.
171        /// The condition type itself implements `Expressive<AnyType>`, enabling
172        /// cross-type chaining like `price.gt(10).eq(false)`.
173        pub trait $trait_name<T>: $crate::vantage_expressions::Expressive<T>
174        where
175            T: Into<$any_type> + Send + Clone + 'static,
176        {
177            /// `field = value`
178            fn eq(&self, value: impl $crate::vantage_expressions::Expressive<T>) -> $condition
179            where
180                Self: Sized,
181            {
182                $crate::condition::build_sql_binary::<T, $any_type, $condition>(
183                    self, value, "{} = {}",
184                )
185            }
186
187            /// `field != value`
188            fn ne(&self, value: impl $crate::vantage_expressions::Expressive<T>) -> $condition
189            where
190                Self: Sized,
191            {
192                $crate::condition::build_sql_binary::<T, $any_type, $condition>(
193                    self, value, "{} != {}",
194                )
195            }
196
197            /// `field > value`
198            fn gt(&self, value: impl $crate::vantage_expressions::Expressive<T>) -> $condition
199            where
200                Self: Sized,
201            {
202                $crate::condition::build_sql_binary::<T, $any_type, $condition>(
203                    self, value, "{} > {}",
204                )
205            }
206
207            /// `field >= value`
208            fn gte(&self, value: impl $crate::vantage_expressions::Expressive<T>) -> $condition
209            where
210                Self: Sized,
211            {
212                $crate::condition::build_sql_binary::<T, $any_type, $condition>(
213                    self, value, "{} >= {}",
214                )
215            }
216
217            /// `field < value`
218            fn lt(&self, value: impl $crate::vantage_expressions::Expressive<T>) -> $condition
219            where
220                Self: Sized,
221            {
222                $crate::condition::build_sql_binary::<T, $any_type, $condition>(
223                    self, value, "{} < {}",
224                )
225            }
226
227            /// `field <= value`
228            fn lte(&self, value: impl $crate::vantage_expressions::Expressive<T>) -> $condition
229            where
230                Self: Sized,
231            {
232                $crate::condition::build_sql_binary::<T, $any_type, $condition>(
233                    self, value, "{} <= {}",
234                )
235            }
236
237            /// `field IN (values_expression)`
238            fn in_(&self, values: impl $crate::vantage_expressions::Expressive<T>) -> $condition
239            where
240                Self: Sized,
241            {
242                $crate::condition::build_sql_binary::<T, $any_type, $condition>(
243                    self,
244                    values,
245                    "{} IN ({})",
246                )
247            }
248
249            /// `field IN (a, b, c)` from a slice of scalar values
250            fn in_list<V: Into<T> + Clone>(&self, values: &[V]) -> $condition
251            where
252                Self: Sized,
253                T: Clone,
254            {
255                use $crate::vantage_expressions::Expression;
256                use $crate::vantage_expressions::traits::expressive::ExpressiveEnum;
257                let params: Vec<Expression<T>> = values
258                    .iter()
259                    .map(|v| Expression::new("{}", vec![ExpressiveEnum::Scalar(v.clone().into())]))
260                    .collect();
261                let expr: Expression<T> = Expression::new(
262                    "{} IN ({})",
263                    vec![
264                        ExpressiveEnum::Nested(self.expr()),
265                        ExpressiveEnum::Nested(Expression::from_vec(params, ", ")),
266                    ],
267                );
268                $condition::from_typed(expr)
269            }
270
271            /// `field NOT IN (values_expression)`
272            fn not_in(&self, values: 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,
278                    values,
279                    "{} NOT IN ({})",
280                )
281            }
282
283            /// `field NOT IN (a, b, c)` from a slice of scalar values
284            fn not_in_list<V: Into<T> + Clone>(&self, values: &[V]) -> $condition
285            where
286                Self: Sized,
287                T: Clone,
288            {
289                use $crate::vantage_expressions::Expression;
290                use $crate::vantage_expressions::traits::expressive::ExpressiveEnum;
291                let params: Vec<Expression<T>> = values
292                    .iter()
293                    .map(|v| Expression::new("{}", vec![ExpressiveEnum::Scalar(v.clone().into())]))
294                    .collect();
295                let expr: Expression<T> = Expression::new(
296                    "{} NOT IN ({})",
297                    vec![
298                        ExpressiveEnum::Nested(self.expr()),
299                        ExpressiveEnum::Nested(Expression::from_vec(params, ", ")),
300                    ],
301                );
302                $condition::from_typed(expr)
303            }
304
305            /// `CAST(expr AS type_name)`
306            fn cast(&self, type_name: &str) -> $condition
307            where
308                Self: Sized,
309            {
310                use $crate::vantage_expressions::Expression;
311                use $crate::vantage_expressions::traits::expressive::ExpressiveEnum;
312                let expr: Expression<T> = Expression::new(
313                    format!("CAST({{}} AS {type_name})"),
314                    vec![ExpressiveEnum::Nested(self.expr())],
315                );
316                $condition::from_typed(expr)
317            }
318
319            /// `field IS NULL`
320            fn is_null(&self) -> $condition
321            where
322                Self: Sized,
323            {
324                use $crate::vantage_expressions::Expression;
325                use $crate::vantage_expressions::traits::expressive::ExpressiveEnum;
326                let expr: Expression<T> =
327                    Expression::new("{} IS NULL", vec![ExpressiveEnum::Nested(self.expr())]);
328                $condition::from_typed(expr)
329            }
330
331            /// `field IS NOT NULL`
332            fn is_not_null(&self) -> $condition
333            where
334                Self: Sized,
335            {
336                use $crate::vantage_expressions::Expression;
337                use $crate::vantage_expressions::traits::expressive::ExpressiveEnum;
338                let expr: Expression<T> =
339                    Expression::new("{} IS NOT NULL", vec![ExpressiveEnum::Nested(self.expr())]);
340                $condition::from_typed(expr)
341            }
342        }
343
344        /// Blanket: any `Expressive<T>` where `T: Into<AnyType>` gets the
345        /// operation trait for free.
346        impl<T, S> $trait_name<T> for S
347        where
348            S: $crate::vantage_expressions::Expressive<T>,
349            T: Into<$any_type> + Send + Clone + 'static,
350        {
351        }
352
353        /// Condition chaining: the condition type wraps `Expression<AnyType>`,
354        /// so implementing `Expressive<AnyType>` gives it the operation trait
355        /// via the blanket above.
356        impl $crate::vantage_expressions::Expressive<$any_type> for $condition {
357            fn expr(&self) -> $crate::vantage_expressions::Expression<$any_type> {
358                self.0.clone()
359            }
360        }
361    };
362}
363
364/// Helper for `define_sql_operation!`: build a binary expression, map to
365/// the backend's condition type. Public so the macro can call it from
366/// any module.
367pub fn build_sql_binary<T, AnyType, Cond>(
368    lhs: &(impl Expressive<T> + ?Sized),
369    rhs: impl Expressive<T>,
370    template: &str,
371) -> Cond
372where
373    T: Into<AnyType> + Send + Clone + 'static,
374    Cond: From<Expression<T>>,
375{
376    let expr: Expression<T> = Expression::new(
377        template,
378        vec![
379            ExpressiveEnum::Nested(lhs.expr()),
380            ExpressiveEnum::Nested(rhs.expr()),
381        ],
382    );
383    Cond::from(expr)
384}