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            /// `(self OR other)` — joins two conditions into a
178            /// `ConditionGroup`.
179            ///
180            /// Use this method to write alternatives. Do not write
181            /// `"a OR b"` as text. The group writes its own brackets,
182            /// and it keeps its meaning next to the other conditions.
183            /// Text has no brackets, and `AND` binds more tightly than
184            /// `OR`. Thus `role = 'admin' AND a OR b` means
185            /// `(role = 'admin' AND a) OR b`.
186            ///
187            /// A chain stays flat: `a.or_(b).or_(c)` gives
188            /// `(a OR b OR c)`.
189            fn or_(
190                &self,
191                other: impl $crate::vantage_expressions::Expressive<T>,
192            ) -> $crate::primitives::ConditionGroup<T>
193            where
194                Self: Sized,
195            {
196                $crate::primitives::or_(self.expr(), other.expr())
197            }
198
199            /// `(self AND other)` — joins two conditions into a
200            /// `ConditionGroup`.
201            ///
202            /// A table joins its conditions with `AND` already. Use this
203            /// method when you must make a group inside an `or_`.
204            fn and_(
205                &self,
206                other: impl $crate::vantage_expressions::Expressive<T>,
207            ) -> $crate::primitives::ConditionGroup<T>
208            where
209                Self: Sized,
210            {
211                $crate::primitives::and_(self.expr(), other.expr())
212            }
213
214            /// `field = value`
215            fn eq(&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 ne(&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 gt(&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 gte(&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 lt(&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 <= value`
265            fn lte(&self, value: 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, value, "{} <= {}",
271                )
272            }
273
274            /// `field IN (values_expression)`
275            fn in_(&self, values: impl $crate::vantage_expressions::Expressive<T>) -> $condition
276            where
277                Self: Sized,
278            {
279                $crate::condition::build_sql_binary::<T, $any_type, $condition>(
280                    self,
281                    values,
282                    "{} IN ({})",
283                )
284            }
285
286            /// `field IN (a, b, c)` from a slice of scalar values
287            fn in_list<V: Into<T> + Clone>(&self, values: &[V]) -> $condition
288            where
289                Self: Sized,
290                T: Clone,
291            {
292                use $crate::vantage_expressions::Expression;
293                use $crate::vantage_expressions::traits::expressive::ExpressiveEnum;
294                let params: Vec<Expression<T>> = values
295                    .iter()
296                    .map(|v| Expression::new("{}", vec![ExpressiveEnum::Scalar(v.clone().into())]))
297                    .collect();
298                let expr: Expression<T> = Expression::new(
299                    "{} IN ({})",
300                    vec![
301                        ExpressiveEnum::Nested(self.expr()),
302                        ExpressiveEnum::Nested(Expression::from_vec(params, ", ")),
303                    ],
304                );
305                $condition::from_typed(expr)
306            }
307
308            /// `field NOT IN (values_expression)`
309            fn not_in(&self, values: impl $crate::vantage_expressions::Expressive<T>) -> $condition
310            where
311                Self: Sized,
312            {
313                $crate::condition::build_sql_binary::<T, $any_type, $condition>(
314                    self,
315                    values,
316                    "{} NOT IN ({})",
317                )
318            }
319
320            /// `field NOT IN (a, b, c)` from a slice of scalar values
321            fn not_in_list<V: Into<T> + Clone>(&self, values: &[V]) -> $condition
322            where
323                Self: Sized,
324                T: Clone,
325            {
326                use $crate::vantage_expressions::Expression;
327                use $crate::vantage_expressions::traits::expressive::ExpressiveEnum;
328                let params: Vec<Expression<T>> = values
329                    .iter()
330                    .map(|v| Expression::new("{}", vec![ExpressiveEnum::Scalar(v.clone().into())]))
331                    .collect();
332                let expr: Expression<T> = Expression::new(
333                    "{} NOT IN ({})",
334                    vec![
335                        ExpressiveEnum::Nested(self.expr()),
336                        ExpressiveEnum::Nested(Expression::from_vec(params, ", ")),
337                    ],
338                );
339                $condition::from_typed(expr)
340            }
341
342            /// `CAST(expr AS type_name)`
343            fn cast(&self, type_name: &str) -> $condition
344            where
345                Self: Sized,
346            {
347                use $crate::vantage_expressions::Expression;
348                use $crate::vantage_expressions::traits::expressive::ExpressiveEnum;
349                let expr: Expression<T> = Expression::new(
350                    format!("CAST({{}} AS {type_name})"),
351                    vec![ExpressiveEnum::Nested(self.expr())],
352                );
353                $condition::from_typed(expr)
354            }
355
356            /// `field IS NULL`
357            fn is_null(&self) -> $condition
358            where
359                Self: Sized,
360            {
361                use $crate::vantage_expressions::Expression;
362                use $crate::vantage_expressions::traits::expressive::ExpressiveEnum;
363                let expr: Expression<T> =
364                    Expression::new("{} IS NULL", vec![ExpressiveEnum::Nested(self.expr())]);
365                $condition::from_typed(expr)
366            }
367
368            /// `field IS NOT NULL`
369            fn is_not_null(&self) -> $condition
370            where
371                Self: Sized,
372            {
373                use $crate::vantage_expressions::Expression;
374                use $crate::vantage_expressions::traits::expressive::ExpressiveEnum;
375                let expr: Expression<T> =
376                    Expression::new("{} IS NOT NULL", vec![ExpressiveEnum::Nested(self.expr())]);
377                $condition::from_typed(expr)
378            }
379        }
380
381        /// Blanket: any `Expressive<T>` where `T: Into<AnyType>` gets the
382        /// operation trait for free.
383        impl<T, S> $trait_name<T> for S
384        where
385            S: $crate::vantage_expressions::Expressive<T>,
386            T: Into<$any_type> + Send + Clone + 'static,
387        {
388        }
389
390        /// Condition chaining: the condition type wraps `Expression<AnyType>`,
391        /// so implementing `Expressive<AnyType>` gives it the operation trait
392        /// via the blanket above.
393        impl $crate::vantage_expressions::Expressive<$any_type> for $condition {
394            fn expr(&self) -> $crate::vantage_expressions::Expression<$any_type> {
395                self.0.clone()
396            }
397        }
398    };
399}
400
401/// Helper for `define_sql_operation!`: build a binary expression, map to
402/// the backend's condition type. Public so the macro can call it from
403/// any module.
404pub fn build_sql_binary<T, AnyType, Cond>(
405    lhs: &(impl Expressive<T> + ?Sized),
406    rhs: impl Expressive<T>,
407    template: &str,
408) -> Cond
409where
410    T: Into<AnyType> + Send + Clone + 'static,
411    Cond: From<Expression<T>>,
412{
413    let expr: Expression<T> = Expression::new(
414        template,
415        vec![
416            ExpressiveEnum::Nested(lhs.expr()),
417            ExpressiveEnum::Nested(rhs.expr()),
418        ],
419    );
420    Cond::from(expr)
421}