rorm_sql/
conditional.rs

1use std::fmt::{Debug, Error, Write};
2
3#[cfg(feature = "mysql")]
4use crate::db_specific::mysql;
5#[cfg(feature = "postgres")]
6use crate::db_specific::postgres;
7#[cfg(feature = "sqlite")]
8use crate::db_specific::sqlite;
9use crate::value::{NullType, Value};
10use crate::DBImpl;
11
12/**
13Trait implementing constructing sql queries from a condition tree.
14
15This trait auto implements `build` which has a simpler api from the more complex `build_to_writer`.
16 */
17pub trait BuildCondition<'a>: 'a {
18    /**
19    This method is used to convert a condition to SQL.
20     */
21    fn build(&self, dialect: DBImpl, lookup: &mut Vec<Value<'a>>) -> String {
22        let mut string = String::new();
23        self.build_to_writer(&mut string, dialect, lookup)
24            .expect("Writing to a string shouldn't fail");
25        string
26    }
27
28    /**
29    This method is used to convert a condition to SQL without allocating a dedicated string.
30     */
31    fn build_to_writer(
32        &self,
33        writer: &mut impl Write,
34        dialect: DBImpl,
35        lookup: &mut Vec<Value<'a>>,
36    ) -> Result<(), Error>;
37}
38
39/**
40This enum represents all available ternary expression.
41*/
42#[derive(Debug, PartialEq, Clone)]
43pub enum TernaryCondition<'a> {
44    /// Between represents "{} BETWEEN {} AND {}" from SQL
45    Between(Box<[Condition<'a>; 3]>),
46    /// Between represents "{} NOT BETWEEN {} AND {}" from SQL
47    NotBetween(Box<[Condition<'a>; 3]>),
48}
49
50impl<'a> BuildCondition<'a> for TernaryCondition<'a> {
51    fn build_to_writer(
52        &self,
53        writer: &mut impl Write,
54        dialect: DBImpl,
55        lookup: &mut Vec<Value<'a>>,
56    ) -> Result<(), Error> {
57        let (keyword, [lhs, mhs, rhs]) = match self {
58            TernaryCondition::Between(params) => ("BETWEEN", params.as_ref()),
59            TernaryCondition::NotBetween(params) => ("NOT BETWEEN", params.as_ref()),
60        };
61        write!(writer, "(")?;
62        lhs.build_to_writer(writer, dialect, lookup)?;
63        write!(writer, " {keyword} ")?;
64        mhs.build_to_writer(writer, dialect, lookup)?;
65        write!(writer, " AND ")?;
66        rhs.build_to_writer(writer, dialect, lookup)?;
67        write!(writer, ")")?;
68        Ok(())
69    }
70}
71
72/**
73This enum represents a binary expression.
74*/
75#[derive(Debug, PartialEq, Clone)]
76pub enum BinaryCondition<'a> {
77    /// Representation of "{} = {}" in SQL
78    Equals(Box<[Condition<'a>; 2]>),
79    /// Representation of "{} <> {}" in SQL
80    NotEquals(Box<[Condition<'a>; 2]>),
81    /// Representation of "{} > {}" in SQL
82    Greater(Box<[Condition<'a>; 2]>),
83    /// Representation of "{} >= {}" in SQL
84    GreaterOrEquals(Box<[Condition<'a>; 2]>),
85    /// Representation of "{} < {}" in SQL
86    Less(Box<[Condition<'a>; 2]>),
87    /// Representation of "{} <= {}" in SQL
88    LessOrEquals(Box<[Condition<'a>; 2]>),
89    /// Representation of "{} LIKE {}" in SQL
90    Like(Box<[Condition<'a>; 2]>),
91    /// Representation of "{} NOT LIKE {}" in SQL
92    NotLike(Box<[Condition<'a>; 2]>),
93    /// Representation of "{} REGEXP {}" in SQL
94    Regexp(Box<[Condition<'a>; 2]>),
95    /// Representation of "{} NOT REGEXP {}" in SQL
96    NotRegexp(Box<[Condition<'a>; 2]>),
97    /// Representation of "{} IN {}" in SQL
98    In(Box<[Condition<'a>; 2]>),
99    /// Representation of "{} NOT IN {}" in SQL
100    NotIn(Box<[Condition<'a>; 2]>),
101    /// Representation of "{} ILIKE {}" in PostgreSQL
102    #[cfg(feature = "postgres-only")]
103    ILike(Box<[Condition<'a>; 2]>),
104    /// Representation of "{} NOT ILIKE {}" in PostgreSQL
105    #[cfg(feature = "postgres-only")]
106    NotILike(Box<[Condition<'a>; 2]>),
107    /// Representation of "{} << {}" for `inet` in PostgreSQL
108    #[cfg(feature = "postgres-only")]
109    Contained(Box<[Condition<'a>; 2]>),
110    /// Representation of "{} <<= {}" for `inet` in PostgreSQL
111    #[cfg(feature = "postgres-only")]
112    ContainedOrEquals(Box<[Condition<'a>; 2]>),
113    /// Representation of "{} >> {}" for `inet` in PostgreSQL
114    #[cfg(feature = "postgres-only")]
115    Contains(Box<[Condition<'a>; 2]>),
116    /// Representation of "{} >>= {}" for `inet` in PostgreSQL
117    #[cfg(feature = "postgres-only")]
118    ContainsOrEquals(Box<[Condition<'a>; 2]>),
119}
120
121impl<'a> BuildCondition<'a> for BinaryCondition<'a> {
122    fn build_to_writer(
123        &self,
124        writer: &mut impl Write,
125        dialect: DBImpl,
126        lookup: &mut Vec<Value<'a>>,
127    ) -> Result<(), Error> {
128        let (keyword, [lhs, rhs]) = match self {
129            BinaryCondition::Equals(params) => ("=", params.as_ref()),
130            BinaryCondition::NotEquals(params) => ("<>", params.as_ref()),
131            BinaryCondition::Greater(params) => (">", params.as_ref()),
132            BinaryCondition::GreaterOrEquals(params) => (">=", params.as_ref()),
133            BinaryCondition::Less(params) => ("<", params.as_ref()),
134            BinaryCondition::LessOrEquals(params) => ("<=", params.as_ref()),
135            BinaryCondition::Like(params) => ("LIKE", params.as_ref()),
136            BinaryCondition::NotLike(params) => ("NOT LIKE", params.as_ref()),
137            BinaryCondition::Regexp(params) => ("REGEXP", params.as_ref()),
138            BinaryCondition::NotRegexp(params) => ("NOT REGEXP", params.as_ref()),
139            BinaryCondition::In(params) => ("IN", params.as_ref()),
140            BinaryCondition::NotIn(params) => ("NOT IN", params.as_ref()),
141            #[cfg(feature = "postgres-only")]
142            BinaryCondition::ILike(params) => ("ILIKE", params.as_ref()),
143            #[cfg(feature = "postgres-only")]
144            BinaryCondition::NotILike(params) => ("NOT ILIKE", params.as_ref()),
145            #[cfg(feature = "postgres-only")]
146            BinaryCondition::Contained(params) => ("<<", params.as_ref()),
147            #[cfg(feature = "postgres-only")]
148            BinaryCondition::ContainedOrEquals(params) => ("<<=", params.as_ref()),
149            #[cfg(feature = "postgres-only")]
150            BinaryCondition::Contains(params) => (">>", params.as_ref()),
151            #[cfg(feature = "postgres-only")]
152            BinaryCondition::ContainsOrEquals(params) => (">>=", params.as_ref()),
153        };
154        write!(writer, "(")?;
155        lhs.build_to_writer(writer, dialect, lookup)?;
156        write!(writer, " {keyword} ")?;
157        rhs.build_to_writer(writer, dialect, lookup)?;
158        #[cfg(feature = "sqlite")]
159        if matches!(dialect, DBImpl::SQLite) && matches!(keyword, "LIKE" | "NOT LIKE") {
160            // Sqlite does not default it
161            write!(writer, " ESCAPE '\'")?;
162        }
163        write!(writer, ")")?;
164        Ok(())
165    }
166}
167
168/**
169This enum represents all available unary conditions.
170*/
171#[derive(Debug, PartialEq, Clone)]
172pub enum UnaryCondition<'a> {
173    /// Representation of SQL's "{} IS NULL"
174    IsNull(Box<Condition<'a>>),
175    /// Representation of SQL's "{} IS NOT NULL"
176    IsNotNull(Box<Condition<'a>>),
177    /// Representation of SQL's "EXISTS {}"
178    Exists(Box<Condition<'a>>),
179    /// Representation of SQL's "NOT EXISTS {}"
180    NotExists(Box<Condition<'a>>),
181    /// Representation of SQL's "NOT {}"
182    Not(Box<Condition<'a>>),
183}
184
185impl<'a> BuildCondition<'a> for UnaryCondition<'a> {
186    fn build_to_writer(
187        &self,
188        writer: &mut impl Write,
189        dialect: DBImpl,
190        lookup: &mut Vec<Value<'a>>,
191    ) -> Result<(), Error> {
192        let (postfix, keyword, value) = match self {
193            UnaryCondition::IsNull(value) => (true, "IS NULL", value.as_ref()),
194            UnaryCondition::IsNotNull(value) => (true, "IS NOT NULL", value.as_ref()),
195            UnaryCondition::Exists(value) => (false, "EXISTS", value.as_ref()),
196            UnaryCondition::NotExists(value) => (false, "NOT EXISTS", value.as_ref()),
197            UnaryCondition::Not(value) => (false, "NOT", value.as_ref()),
198        };
199        write!(writer, "(")?;
200        if postfix {
201            value.build_to_writer(writer, dialect, lookup)?;
202            write!(writer, " {keyword}")?;
203        } else {
204            write!(writer, "{keyword} ")?;
205            value.build_to_writer(writer, dialect, lookup)?;
206        }
207        write!(writer, ")")?;
208        Ok(())
209    }
210}
211
212/**
213This enum represents a condition tree.
214*/
215#[derive(Debug, PartialEq, Clone)]
216pub enum Condition<'a> {
217    /// A list of [Condition]s, that get expanded to "{} AND {} ..."
218    Conjunction(Vec<Condition<'a>>),
219    /// A list of [Condition]s, that get expanded to "{} OR {} ..."
220    Disjunction(Vec<Condition<'a>>),
221    /// Representation of an unary condition.
222    UnaryCondition(UnaryCondition<'a>),
223    /// Representation of a binary condition.
224    BinaryCondition(BinaryCondition<'a>),
225    /// Representation of a ternary condition.
226    TernaryCondition(TernaryCondition<'a>),
227    /// Representation of a value.
228    Value(Value<'a>),
229}
230
231impl<'a> BuildCondition<'a> for Condition<'a> {
232    fn build_to_writer(
233        &self,
234        writer: &mut impl Write,
235        dialect: DBImpl,
236        lookup: &mut Vec<Value<'a>>,
237    ) -> Result<(), Error> {
238        match self {
239            Condition::Conjunction(conditions) | Condition::Disjunction(conditions) => {
240                let keyword = match self {
241                    Condition::Conjunction(_) => "AND ",
242                    Condition::Disjunction(_) => "OR ",
243                    _ => unreachable!("All other possibilities would pass the outer match arm"),
244                };
245                write!(writer, "(")?;
246                if let Some(first) = conditions.first() {
247                    first.build_to_writer(writer, dialect, lookup)?;
248                    conditions.iter().enumerate().try_for_each(|(idx, cond)| {
249                        if idx > 0 {
250                            write!(writer, " {keyword}")?;
251                            cond.build_to_writer(writer, dialect, lookup)?;
252                        }
253                        Ok(())
254                    })?;
255                }
256                write!(writer, ")")?;
257                Ok(())
258            }
259            Condition::UnaryCondition(unary) => unary.build_to_writer(writer, dialect, lookup),
260            Condition::BinaryCondition(binary) => binary.build_to_writer(writer, dialect, lookup),
261            Condition::TernaryCondition(ternary) => {
262                ternary.build_to_writer(writer, dialect, lookup)
263            }
264            Condition::Value(value) => match value {
265                Value::Ident(string) => write!(writer, "{string}"),
266                Value::Column {
267                    table_name,
268                    column_name,
269                } => match dialect {
270                    #[cfg(feature = "sqlite")]
271                    DBImpl::SQLite => {
272                        if let Some(table_name) = table_name {
273                            write!(writer, "\"{table_name}\".")?;
274                        }
275                        write!(writer, "{column_name}")
276                    }
277                    #[cfg(feature = "mysql")]
278                    DBImpl::MySQL => {
279                        if let Some(table_name) = table_name {
280                            write!(writer, "{table_name}.")?;
281                        }
282                        write!(writer, "{column_name}")
283                    }
284                    #[cfg(feature = "postgres")]
285                    DBImpl::Postgres => {
286                        if let Some(table_name) = table_name {
287                            write!(writer, "\"{table_name}\".")?;
288                        }
289                        write!(writer, "{column_name}")
290                    }
291                },
292                Value::Choice(c) => match dialect {
293                    #[cfg(feature = "sqlite")]
294                    DBImpl::SQLite => write!(writer, "{}", sqlite::fmt(c)),
295                    #[cfg(feature = "mysql")]
296                    DBImpl::MySQL => write!(writer, "{}", mysql::fmt(c)),
297                    #[cfg(feature = "postgres")]
298                    DBImpl::Postgres => write!(writer, "{}", postgres::fmt(c)),
299                },
300                Value::Null(NullType::Choice) => write!(writer, "NULL"),
301
302                _ => {
303                    lookup.push(*value);
304                    match dialect {
305                        #[cfg(feature = "sqlite")]
306                        DBImpl::SQLite => {
307                            write!(writer, "?")
308                        }
309                        #[cfg(feature = "mysql")]
310                        DBImpl::MySQL => {
311                            write!(writer, "?")
312                        }
313                        #[cfg(feature = "postgres")]
314                        DBImpl::Postgres => {
315                            write!(writer, "${}", lookup.len())
316                        }
317                    }
318                }
319            },
320        }
321    }
322}
323
324/**
325This macro is used to simplify the creation of conjunctive [Condition]s.
326It takes a variadic amount of conditions and places them in a [Condition::Conjunction].
327
328It does **not** try to simplify any conditions where one or no conditions are passed,
329so no one gets confused. This also ensures, that the return type of this macro
330is always [Condition::Conjunction].
331
332**Usage**:
333
334```
335use rorm_sql::and;
336use rorm_sql::conditional::Condition;
337use rorm_sql::conditional::BinaryCondition;
338use rorm_sql::value::Value;
339
340let condition = and!(
341    Condition::BinaryCondition(
342        BinaryCondition::Equals(Box::new([
343            Condition::Value(Value::Ident("id")),
344            Condition::Value(Value::I64(23)),
345        ]))
346    ),
347    Condition::BinaryCondition(
348        BinaryCondition::Like(Box::new([
349            Condition::Value(Value::Ident("foo")),
350            Condition::Value(Value::String("%bar")),
351        ]))
352    ),
353);
354```
355*/
356#[macro_export]
357macro_rules! and {
358    () => {{
359        $crate::conditional::Condition::Conjunction(vec![])
360    }};
361    ($($cond:expr),+ $(,)?) => {{
362        $crate::conditional::Condition::Conjunction(vec![$($cond),+])
363    }};
364}
365
366/**
367This macro is used to simplify the creation of disjunctive [Condition]s.
368It takes a variadic amount of conditions and places them in a [Condition::Disjunction].
369
370It does **not** try to simplify any conditions where one or no conditions are passed,
371so no one gets confused. This also ensures, that the return type of this macro
372is always [Condition::Disjunction].
373
374**Usage**:
375
376```
377use rorm_sql::or;
378use rorm_sql::conditional::Condition;
379use rorm_sql::conditional::BinaryCondition;
380use rorm_sql::value::Value;
381
382let condition = or!(
383    Condition::BinaryCondition(
384        BinaryCondition::Equals(Box::new([
385            Condition::Value(Value::Ident("id")),
386            Condition::Value(Value::I64(23)),
387        ]))
388    ),
389    Condition::BinaryCondition(
390        BinaryCondition::Like(Box::new([
391            Condition::Value(Value::Ident("foo")),
392            Condition::Value(Value::String("%bar")),
393        ]))
394    ),
395);
396```
397 */
398#[macro_export]
399macro_rules! or {
400    () => {{
401        $crate::conditional::Condition::Disjunction(vec![])
402    }};
403    ($($cond:expr),+ $(,)?) => {{
404        $crate::conditional::Condition::Disjunction(vec![$($cond),+])
405    }};
406}
407
408#[cfg(test)]
409mod test {
410    use crate::conditional::Condition;
411    use crate::value::Value;
412
413    #[test]
414    fn empty_and() {
415        assert_eq!(and!(), Condition::Conjunction(vec![]))
416    }
417
418    #[test]
419    fn empty_or() {
420        assert_eq!(or!(), Condition::Disjunction(vec![]))
421    }
422
423    #[test]
424    fn and_01() {
425        assert_eq!(
426            and!(Condition::Value(Value::String("foo"))),
427            Condition::Conjunction(vec![Condition::Value(Value::String("foo"))])
428        );
429    }
430    #[test]
431    fn and_02() {
432        assert_eq!(
433            and!(
434                Condition::Value(Value::String("foo")),
435                Condition::Value(Value::String("foo"))
436            ),
437            Condition::Conjunction(vec![
438                Condition::Value(Value::String("foo")),
439                Condition::Value(Value::String("foo"))
440            ])
441        );
442    }
443
444    #[test]
445    fn or_01() {
446        assert_eq!(
447            or!(Condition::Value(Value::String("foo"))),
448            Condition::Disjunction(vec![Condition::Value(Value::String("foo"))])
449        );
450    }
451    #[test]
452    fn or_02() {
453        assert_eq!(
454            or!(
455                Condition::Value(Value::String("foo")),
456                Condition::Value(Value::String("foo"))
457            ),
458            Condition::Disjunction(vec![
459                Condition::Value(Value::String("foo")),
460                Condition::Value(Value::String("foo"))
461            ])
462        );
463    }
464}