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}
102
103impl<'a> BuildCondition<'a> for BinaryCondition<'a> {
104    fn build_to_writer(
105        &self,
106        writer: &mut impl Write,
107        dialect: DBImpl,
108        lookup: &mut Vec<Value<'a>>,
109    ) -> Result<(), Error> {
110        let (keyword, [lhs, rhs]) = match self {
111            BinaryCondition::Equals(params) => ("=", params.as_ref()),
112            BinaryCondition::NotEquals(params) => ("<>", params.as_ref()),
113            BinaryCondition::Greater(params) => (">", params.as_ref()),
114            BinaryCondition::GreaterOrEquals(params) => (">=", params.as_ref()),
115            BinaryCondition::Less(params) => ("<", params.as_ref()),
116            BinaryCondition::LessOrEquals(params) => ("<=", params.as_ref()),
117            BinaryCondition::Like(params) => ("LIKE", params.as_ref()),
118            BinaryCondition::NotLike(params) => ("NOT LIKE", params.as_ref()),
119            BinaryCondition::Regexp(params) => ("REGEXP", params.as_ref()),
120            BinaryCondition::NotRegexp(params) => ("NOT REGEXP", params.as_ref()),
121            BinaryCondition::In(params) => ("IN", params.as_ref()),
122            BinaryCondition::NotIn(params) => ("NOT IN", params.as_ref()),
123        };
124        write!(writer, "(")?;
125        lhs.build_to_writer(writer, dialect, lookup)?;
126        write!(writer, " {keyword} ")?;
127        rhs.build_to_writer(writer, dialect, lookup)?;
128        write!(writer, ")")?;
129        Ok(())
130    }
131}
132
133/**
134This enum represents all available unary conditions.
135*/
136#[derive(Debug, PartialEq, Clone)]
137pub enum UnaryCondition<'a> {
138    /// Representation of SQL's "{} IS NULL"
139    IsNull(Box<Condition<'a>>),
140    /// Representation of SQL's "{} IS NOT NULL"
141    IsNotNull(Box<Condition<'a>>),
142    /// Representation of SQL's "EXISTS {}"
143    Exists(Box<Condition<'a>>),
144    /// Representation of SQL's "NOT EXISTS {}"
145    NotExists(Box<Condition<'a>>),
146    /// Representation of SQL's "NOT {}"
147    Not(Box<Condition<'a>>),
148}
149
150impl<'a> BuildCondition<'a> for UnaryCondition<'a> {
151    fn build_to_writer(
152        &self,
153        writer: &mut impl Write,
154        dialect: DBImpl,
155        lookup: &mut Vec<Value<'a>>,
156    ) -> Result<(), Error> {
157        let (postfix, keyword, value) = match self {
158            UnaryCondition::IsNull(value) => (true, "IS NULL", value.as_ref()),
159            UnaryCondition::IsNotNull(value) => (true, "IS NOT NULL", value.as_ref()),
160            UnaryCondition::Exists(value) => (false, "EXISTS", value.as_ref()),
161            UnaryCondition::NotExists(value) => (false, "NOT EXISTS", value.as_ref()),
162            UnaryCondition::Not(value) => (false, "NOT", value.as_ref()),
163        };
164        write!(writer, "(")?;
165        if postfix {
166            value.build_to_writer(writer, dialect, lookup)?;
167            write!(writer, " {keyword}")?;
168        } else {
169            write!(writer, "{keyword} ")?;
170            value.build_to_writer(writer, dialect, lookup)?;
171        }
172        write!(writer, ")")?;
173        Ok(())
174    }
175}
176
177/**
178This enum represents a condition tree.
179*/
180#[derive(Debug, PartialEq, Clone)]
181pub enum Condition<'a> {
182    /// A list of [Condition]s, that get expanded to "{} AND {} ..."
183    Conjunction(Vec<Condition<'a>>),
184    /// A list of [Condition]s, that get expanded to "{} OR {} ..."
185    Disjunction(Vec<Condition<'a>>),
186    /// Representation of an unary condition.
187    UnaryCondition(UnaryCondition<'a>),
188    /// Representation of a binary condition.
189    BinaryCondition(BinaryCondition<'a>),
190    /// Representation of a ternary condition.
191    TernaryCondition(TernaryCondition<'a>),
192    /// Representation of a value.
193    Value(Value<'a>),
194}
195
196impl<'a> BuildCondition<'a> for Condition<'a> {
197    fn build_to_writer(
198        &self,
199        writer: &mut impl Write,
200        dialect: DBImpl,
201        lookup: &mut Vec<Value<'a>>,
202    ) -> Result<(), Error> {
203        match self {
204            Condition::Conjunction(conditions) | Condition::Disjunction(conditions) => {
205                let keyword = match self {
206                    Condition::Conjunction(_) => "AND ",
207                    Condition::Disjunction(_) => "OR ",
208                    _ => unreachable!("All other possibilities would pass the outer match arm"),
209                };
210                write!(writer, "(")?;
211                if let Some(first) = conditions.first() {
212                    first.build_to_writer(writer, dialect, lookup)?;
213                    conditions.iter().enumerate().try_for_each(|(idx, cond)| {
214                        if idx > 0 {
215                            write!(writer, " {keyword}")?;
216                            cond.build_to_writer(writer, dialect, lookup)?;
217                        }
218                        Ok(())
219                    })?;
220                }
221                write!(writer, ")")?;
222                Ok(())
223            }
224            Condition::UnaryCondition(unary) => unary.build_to_writer(writer, dialect, lookup),
225            Condition::BinaryCondition(binary) => binary.build_to_writer(writer, dialect, lookup),
226            Condition::TernaryCondition(ternary) => {
227                ternary.build_to_writer(writer, dialect, lookup)
228            }
229            Condition::Value(value) => match value {
230                Value::Ident(string) => write!(writer, "{string}"),
231                Value::Column {
232                    table_name,
233                    column_name,
234                } => match dialect {
235                    #[cfg(feature = "sqlite")]
236                    DBImpl::SQLite => {
237                        if let Some(table_name) = table_name {
238                            write!(writer, "\"{table_name}\".")?;
239                        }
240                        write!(writer, "{column_name}")
241                    }
242                    #[cfg(feature = "mysql")]
243                    DBImpl::MySQL => {
244                        if let Some(table_name) = table_name {
245                            write!(writer, "{table_name}.")?;
246                        }
247                        write!(writer, "{column_name}")
248                    }
249                    #[cfg(feature = "postgres")]
250                    DBImpl::Postgres => {
251                        if let Some(table_name) = table_name {
252                            write!(writer, "\"{table_name}\".")?;
253                        }
254                        write!(writer, "{column_name}")
255                    }
256                },
257                Value::Choice(c) => match dialect {
258                    #[cfg(feature = "sqlite")]
259                    DBImpl::SQLite => write!(writer, "{}", sqlite::fmt(c)),
260                    #[cfg(feature = "mysql")]
261                    DBImpl::MySQL => write!(writer, "{}", mysql::fmt(c)),
262                    #[cfg(feature = "postgres")]
263                    DBImpl::Postgres => write!(writer, "{}", postgres::fmt(c)),
264                },
265                Value::Null(NullType::Choice) => write!(writer, "NULL"),
266
267                _ => {
268                    lookup.push(*value);
269                    match dialect {
270                        #[cfg(feature = "sqlite")]
271                        DBImpl::SQLite => {
272                            write!(writer, "?")
273                        }
274                        #[cfg(feature = "mysql")]
275                        DBImpl::MySQL => {
276                            write!(writer, "?")
277                        }
278                        #[cfg(feature = "postgres")]
279                        DBImpl::Postgres => {
280                            write!(writer, "${}", lookup.len())
281                        }
282                    }
283                }
284            },
285        }
286    }
287}
288
289/**
290This macro is used to simplify the creation of conjunctive [Condition]s.
291It takes a variadic amount of conditions and places them in a [Condition::Conjunction].
292
293It does **not** try to simplify any conditions where one or no conditions are passed,
294so no one gets confused. This also ensures, that the return type of this macro
295is always [Condition::Conjunction].
296
297**Usage**:
298
299```
300use rorm_sql::and;
301use rorm_sql::conditional::Condition;
302use rorm_sql::conditional::BinaryCondition;
303use rorm_sql::value::Value;
304
305let condition = and!(
306    Condition::BinaryCondition(
307        BinaryCondition::Equals(Box::new([
308            Condition::Value(Value::Ident("id")),
309            Condition::Value(Value::I64(23)),
310        ]))
311    ),
312    Condition::BinaryCondition(
313        BinaryCondition::Like(Box::new([
314            Condition::Value(Value::Ident("foo")),
315            Condition::Value(Value::String("%bar")),
316        ]))
317    ),
318);
319```
320*/
321#[macro_export]
322macro_rules! and {
323    () => {{
324        $crate::conditional::Condition::Conjunction(vec![])
325    }};
326    ($($cond:expr),+ $(,)?) => {{
327        $crate::conditional::Condition::Conjunction(vec![$($cond),+])
328    }};
329}
330
331/**
332This macro is used to simplify the creation of disjunctive [Condition]s.
333It takes a variadic amount of conditions and places them in a [Condition::Disjunction].
334
335It does **not** try to simplify any conditions where one or no conditions are passed,
336so no one gets confused. This also ensures, that the return type of this macro
337is always [Condition::Disjunction].
338
339**Usage**:
340
341```
342use rorm_sql::or;
343use rorm_sql::conditional::Condition;
344use rorm_sql::conditional::BinaryCondition;
345use rorm_sql::value::Value;
346
347let condition = or!(
348    Condition::BinaryCondition(
349        BinaryCondition::Equals(Box::new([
350            Condition::Value(Value::Ident("id")),
351            Condition::Value(Value::I64(23)),
352        ]))
353    ),
354    Condition::BinaryCondition(
355        BinaryCondition::Like(Box::new([
356            Condition::Value(Value::Ident("foo")),
357            Condition::Value(Value::String("%bar")),
358        ]))
359    ),
360);
361```
362 */
363#[macro_export]
364macro_rules! or {
365    () => {{
366        $crate::conditional::Condition::Disjunction(vec![])
367    }};
368    ($($cond:expr),+ $(,)?) => {{
369        $crate::conditional::Condition::Disjunction(vec![$($cond),+])
370    }};
371}
372
373#[cfg(test)]
374mod test {
375    use crate::conditional::Condition;
376    use crate::value::Value;
377
378    #[test]
379    fn empty_and() {
380        assert_eq!(and!(), Condition::Conjunction(vec![]))
381    }
382
383    #[test]
384    fn empty_or() {
385        assert_eq!(or!(), Condition::Disjunction(vec![]))
386    }
387
388    #[test]
389    fn and_01() {
390        assert_eq!(
391            and!(Condition::Value(Value::String("foo"))),
392            Condition::Conjunction(vec![Condition::Value(Value::String("foo"))])
393        );
394    }
395    #[test]
396    fn and_02() {
397        assert_eq!(
398            and!(
399                Condition::Value(Value::String("foo")),
400                Condition::Value(Value::String("foo"))
401            ),
402            Condition::Conjunction(vec![
403                Condition::Value(Value::String("foo")),
404                Condition::Value(Value::String("foo"))
405            ])
406        );
407    }
408
409    #[test]
410    fn or_01() {
411        assert_eq!(
412            or!(Condition::Value(Value::String("foo"))),
413            Condition::Disjunction(vec![Condition::Value(Value::String("foo"))])
414        );
415    }
416    #[test]
417    fn or_02() {
418        assert_eq!(
419            or!(
420                Condition::Value(Value::String("foo")),
421                Condition::Value(Value::String("foo"))
422            ),
423            Condition::Disjunction(vec![
424                Condition::Value(Value::String("foo")),
425                Condition::Value(Value::String("foo"))
426            ])
427        );
428    }
429}