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
10pub trait BuildCondition<'a>: 'a {
16 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 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#[derive(Debug, PartialEq, Clone)]
41pub enum TernaryCondition<'a> {
42 Between(Box<[Condition<'a>; 3]>),
44 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#[derive(Debug, PartialEq, Clone)]
74pub enum BinaryCondition<'a> {
75 Equals(Box<[Condition<'a>; 2]>),
77 NotEquals(Box<[Condition<'a>; 2]>),
79 Greater(Box<[Condition<'a>; 2]>),
81 GreaterOrEquals(Box<[Condition<'a>; 2]>),
83 Less(Box<[Condition<'a>; 2]>),
85 LessOrEquals(Box<[Condition<'a>; 2]>),
87 Like(Box<[Condition<'a>; 2]>),
89 NotLike(Box<[Condition<'a>; 2]>),
91 Regexp(Box<[Condition<'a>; 2]>),
93 NotRegexp(Box<[Condition<'a>; 2]>),
95 In(Box<[Condition<'a>; 2]>),
97 NotIn(Box<[Condition<'a>; 2]>),
99 #[cfg(feature = "postgres-only")]
101 ILike(Box<[Condition<'a>; 2]>),
102 #[cfg(feature = "postgres-only")]
104 NotILike(Box<[Condition<'a>; 2]>),
105 #[cfg(feature = "postgres-only")]
107 Contained(Box<[Condition<'a>; 2]>),
108 #[cfg(feature = "postgres-only")]
110 ContainedOrEquals(Box<[Condition<'a>; 2]>),
111 #[cfg(feature = "postgres-only")]
113 Contains(Box<[Condition<'a>; 2]>),
114 #[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 write!(writer, " ESCAPE '\'")?;
160 }
161 write!(writer, ")")?;
162 Ok(())
163 }
164}
165
166#[derive(Debug, PartialEq, Clone)]
170pub enum UnaryCondition<'a> {
171 IsNull(Box<Condition<'a>>),
173 IsNotNull(Box<Condition<'a>>),
175 Exists(Box<Condition<'a>>),
177 NotExists(Box<Condition<'a>>),
179 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#[derive(Debug, PartialEq, Clone)]
214pub enum Condition<'a> {
215 Conjunction(Vec<Condition<'a>>),
217 Disjunction(Vec<Condition<'a>>),
219 UnaryCondition(UnaryCondition<'a>),
221 BinaryCondition(BinaryCondition<'a>),
223 TernaryCondition(TernaryCondition<'a>),
225 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#[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#[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}