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