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
12pub trait BuildCondition<'a>: 'a {
18 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 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#[derive(Debug, PartialEq, Clone)]
43pub enum TernaryCondition<'a> {
44 Between(Box<[Condition<'a>; 3]>),
46 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#[derive(Debug, PartialEq, Clone)]
76pub enum BinaryCondition<'a> {
77 Equals(Box<[Condition<'a>; 2]>),
79 NotEquals(Box<[Condition<'a>; 2]>),
81 Greater(Box<[Condition<'a>; 2]>),
83 GreaterOrEquals(Box<[Condition<'a>; 2]>),
85 Less(Box<[Condition<'a>; 2]>),
87 LessOrEquals(Box<[Condition<'a>; 2]>),
89 Like(Box<[Condition<'a>; 2]>),
91 NotLike(Box<[Condition<'a>; 2]>),
93 Regexp(Box<[Condition<'a>; 2]>),
95 NotRegexp(Box<[Condition<'a>; 2]>),
97 In(Box<[Condition<'a>; 2]>),
99 NotIn(Box<[Condition<'a>; 2]>),
101 #[cfg(feature = "postgres-only")]
103 ILike(Box<[Condition<'a>; 2]>),
104 #[cfg(feature = "postgres-only")]
106 NotILike(Box<[Condition<'a>; 2]>),
107 #[cfg(feature = "postgres-only")]
109 Contained(Box<[Condition<'a>; 2]>),
110 #[cfg(feature = "postgres-only")]
112 ContainedOrEquals(Box<[Condition<'a>; 2]>),
113 #[cfg(feature = "postgres-only")]
115 Contains(Box<[Condition<'a>; 2]>),
116 #[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 write!(writer, " ESCAPE '\'")?;
162 }
163 write!(writer, ")")?;
164 Ok(())
165 }
166}
167
168#[derive(Debug, PartialEq, Clone)]
172pub enum UnaryCondition<'a> {
173 IsNull(Box<Condition<'a>>),
175 IsNotNull(Box<Condition<'a>>),
177 Exists(Box<Condition<'a>>),
179 NotExists(Box<Condition<'a>>),
181 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#[derive(Debug, PartialEq, Clone)]
216pub enum Condition<'a> {
217 Conjunction(Vec<Condition<'a>>),
219 Disjunction(Vec<Condition<'a>>),
221 UnaryCondition(UnaryCondition<'a>),
223 BinaryCondition(BinaryCondition<'a>),
225 TernaryCondition(TernaryCondition<'a>),
227 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#[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#[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}