use std::fmt::{Debug, Error, Write};
#[cfg(feature = "mysql")]
use crate::db_specific::mysql;
#[cfg(feature = "postgres")]
use crate::db_specific::postgres;
#[cfg(feature = "sqlite")]
use crate::db_specific::sqlite;
use crate::value::{NullType, Value};
use crate::DBImpl;
pub trait BuildCondition<'a>: 'a {
fn build(&self, dialect: DBImpl, lookup: &mut Vec<Value<'a>>) -> String {
let mut string = String::new();
self.build_to_writer(&mut string, dialect, lookup)
.expect("Writing to a string shouldn't fail");
string
}
fn build_to_writer(
&self,
writer: &mut impl Write,
dialect: DBImpl,
lookup: &mut Vec<Value<'a>>,
) -> Result<(), Error>;
}
#[derive(Debug, PartialEq, Clone)]
pub enum TernaryCondition<'a> {
Between(Box<[Condition<'a>; 3]>),
NotBetween(Box<[Condition<'a>; 3]>),
}
impl<'a> BuildCondition<'a> for TernaryCondition<'a> {
fn build_to_writer(
&self,
writer: &mut impl Write,
dialect: DBImpl,
lookup: &mut Vec<Value<'a>>,
) -> Result<(), Error> {
let (keyword, [lhs, mhs, rhs]) = match self {
TernaryCondition::Between(params) => ("BETWEEN", params.as_ref()),
TernaryCondition::NotBetween(params) => ("NOT BETWEEN", params.as_ref()),
};
write!(writer, "(")?;
lhs.build_to_writer(writer, dialect, lookup)?;
write!(writer, " {keyword} ")?;
mhs.build_to_writer(writer, dialect, lookup)?;
write!(writer, " AND ")?;
rhs.build_to_writer(writer, dialect, lookup)?;
write!(writer, ")")?;
Ok(())
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum BinaryCondition<'a> {
Equals(Box<[Condition<'a>; 2]>),
NotEquals(Box<[Condition<'a>; 2]>),
Greater(Box<[Condition<'a>; 2]>),
GreaterOrEquals(Box<[Condition<'a>; 2]>),
Less(Box<[Condition<'a>; 2]>),
LessOrEquals(Box<[Condition<'a>; 2]>),
Like(Box<[Condition<'a>; 2]>),
NotLike(Box<[Condition<'a>; 2]>),
Regexp(Box<[Condition<'a>; 2]>),
NotRegexp(Box<[Condition<'a>; 2]>),
In(Box<[Condition<'a>; 2]>),
NotIn(Box<[Condition<'a>; 2]>),
}
impl<'a> BuildCondition<'a> for BinaryCondition<'a> {
fn build_to_writer(
&self,
writer: &mut impl Write,
dialect: DBImpl,
lookup: &mut Vec<Value<'a>>,
) -> Result<(), Error> {
let (keyword, [lhs, rhs]) = match self {
BinaryCondition::Equals(params) => ("=", params.as_ref()),
BinaryCondition::NotEquals(params) => ("<>", params.as_ref()),
BinaryCondition::Greater(params) => (">", params.as_ref()),
BinaryCondition::GreaterOrEquals(params) => (">=", params.as_ref()),
BinaryCondition::Less(params) => ("<", params.as_ref()),
BinaryCondition::LessOrEquals(params) => ("<=", params.as_ref()),
BinaryCondition::Like(params) => ("LIKE", params.as_ref()),
BinaryCondition::NotLike(params) => ("NOT LIKE", params.as_ref()),
BinaryCondition::Regexp(params) => ("REGEXP", params.as_ref()),
BinaryCondition::NotRegexp(params) => ("NOT REGEXP", params.as_ref()),
BinaryCondition::In(params) => ("IN", params.as_ref()),
BinaryCondition::NotIn(params) => ("NOT IN", params.as_ref()),
};
write!(writer, "(")?;
lhs.build_to_writer(writer, dialect, lookup)?;
write!(writer, " {keyword} ")?;
rhs.build_to_writer(writer, dialect, lookup)?;
write!(writer, ")")?;
Ok(())
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum UnaryCondition<'a> {
IsNull(Box<Condition<'a>>),
IsNotNull(Box<Condition<'a>>),
Exists(Box<Condition<'a>>),
NotExists(Box<Condition<'a>>),
Not(Box<Condition<'a>>),
}
impl<'a> BuildCondition<'a> for UnaryCondition<'a> {
fn build_to_writer(
&self,
writer: &mut impl Write,
dialect: DBImpl,
lookup: &mut Vec<Value<'a>>,
) -> Result<(), Error> {
let (postfix, keyword, value) = match self {
UnaryCondition::IsNull(value) => (true, "IS NULL", value.as_ref()),
UnaryCondition::IsNotNull(value) => (true, "IS NOT NULL", value.as_ref()),
UnaryCondition::Exists(value) => (false, "EXISTS", value.as_ref()),
UnaryCondition::NotExists(value) => (false, "NOT EXISTS", value.as_ref()),
UnaryCondition::Not(value) => (false, "NOT", value.as_ref()),
};
write!(writer, "(")?;
if postfix {
value.build_to_writer(writer, dialect, lookup)?;
write!(writer, " {keyword}")?;
} else {
write!(writer, "{keyword} ")?;
value.build_to_writer(writer, dialect, lookup)?;
}
write!(writer, ")")?;
Ok(())
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum Condition<'a> {
Conjunction(Vec<Condition<'a>>),
Disjunction(Vec<Condition<'a>>),
UnaryCondition(UnaryCondition<'a>),
BinaryCondition(BinaryCondition<'a>),
TernaryCondition(TernaryCondition<'a>),
Value(Value<'a>),
}
impl<'a> BuildCondition<'a> for Condition<'a> {
fn build_to_writer(
&self,
writer: &mut impl Write,
dialect: DBImpl,
lookup: &mut Vec<Value<'a>>,
) -> Result<(), Error> {
match self {
Condition::Conjunction(conditions) | Condition::Disjunction(conditions) => {
let keyword = match self {
Condition::Conjunction(_) => "AND ",
Condition::Disjunction(_) => "OR ",
_ => unreachable!("All other possibilities would pass the outer match arm"),
};
write!(writer, "(")?;
if let Some(first) = conditions.first() {
first.build_to_writer(writer, dialect, lookup)?;
conditions.iter().enumerate().try_for_each(|(idx, cond)| {
if idx > 0 {
write!(writer, " {keyword}")?;
cond.build_to_writer(writer, dialect, lookup)?;
}
Ok(())
})?;
}
write!(writer, ")")?;
Ok(())
}
Condition::UnaryCondition(unary) => unary.build_to_writer(writer, dialect, lookup),
Condition::BinaryCondition(binary) => binary.build_to_writer(writer, dialect, lookup),
Condition::TernaryCondition(ternary) => {
ternary.build_to_writer(writer, dialect, lookup)
}
Condition::Value(value) => match value {
Value::Ident(string) => write!(writer, "{string}"),
Value::Column {
table_name,
column_name,
} => match dialect {
#[cfg(feature = "sqlite")]
DBImpl::SQLite => {
if let Some(table_name) = table_name {
write!(writer, "\"{table_name}\".")?;
}
write!(writer, "{column_name}")
}
#[cfg(feature = "mysql")]
DBImpl::MySQL => {
if let Some(table_name) = table_name {
write!(writer, "{table_name}.")?;
}
write!(writer, "{column_name}")
}
#[cfg(feature = "postgres")]
DBImpl::Postgres => {
if let Some(table_name) = table_name {
write!(writer, "\"{table_name}\".")?;
}
write!(writer, "{column_name}")
}
},
Value::Choice(c) => match dialect {
#[cfg(feature = "sqlite")]
DBImpl::SQLite => write!(writer, "{}", sqlite::fmt(c)),
#[cfg(feature = "mysql")]
DBImpl::MySQL => write!(writer, "{}", mysql::fmt(c)),
#[cfg(feature = "postgres")]
DBImpl::Postgres => write!(writer, "{}", postgres::fmt(c)),
},
Value::Null(NullType::Choice) => write!(writer, "NULL"),
_ => {
lookup.push(*value);
match dialect {
#[cfg(feature = "sqlite")]
DBImpl::SQLite => {
write!(writer, "?")
}
#[cfg(feature = "mysql")]
DBImpl::MySQL => {
write!(writer, "?")
}
#[cfg(feature = "postgres")]
DBImpl::Postgres => {
write!(writer, "${}", lookup.len())
}
}
}
},
}
}
}
#[macro_export]
macro_rules! and {
() => {{
$crate::conditional::Condition::Conjunction(vec![])
}};
($($cond:expr),+ $(,)?) => {{
$crate::conditional::Condition::Conjunction(vec![$($cond),+])
}};
}
#[macro_export]
macro_rules! or {
() => {{
$crate::conditional::Condition::Disjunction(vec![])
}};
($($cond:expr),+ $(,)?) => {{
$crate::conditional::Condition::Disjunction(vec![$($cond),+])
}};
}
#[cfg(test)]
mod test {
use crate::conditional::Condition;
use crate::value::Value;
#[test]
fn empty_and() {
assert_eq!(and!(), Condition::Conjunction(vec![]))
}
#[test]
fn empty_or() {
assert_eq!(or!(), Condition::Disjunction(vec![]))
}
#[test]
fn and_01() {
assert_eq!(
and!(Condition::Value(Value::String("foo"))),
Condition::Conjunction(vec![Condition::Value(Value::String("foo"))])
);
}
#[test]
fn and_02() {
assert_eq!(
and!(
Condition::Value(Value::String("foo")),
Condition::Value(Value::String("foo"))
),
Condition::Conjunction(vec![
Condition::Value(Value::String("foo")),
Condition::Value(Value::String("foo"))
])
);
}
#[test]
fn or_01() {
assert_eq!(
or!(Condition::Value(Value::String("foo"))),
Condition::Disjunction(vec![Condition::Value(Value::String("foo"))])
);
}
#[test]
fn or_02() {
assert_eq!(
or!(
Condition::Value(Value::String("foo")),
Condition::Value(Value::String("foo"))
),
Condition::Disjunction(vec![
Condition::Value(Value::String("foo")),
Condition::Value(Value::String("foo"))
])
);
}
}