Skip to main content

rorm_sql/
conditional.rs

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