use crate::{expr::SimpleExpr, types::LogicalChainOper};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConditionType {
Any,
All,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Condition {
pub(crate) negate: bool,
pub(crate) condition_type: ConditionType,
pub(crate) conditions: Vec<ConditionExpression>,
}
pub trait IntoCondition {
fn into_condition(self) -> Condition;
}
pub type Cond = Condition;
#[derive(Debug, Clone, PartialEq)]
pub enum ConditionExpression {
Condition(Condition),
SimpleExpr(SimpleExpr),
}
#[derive(Default, Debug, Clone, PartialEq)]
pub enum ConditionHolderContents {
#[default]
Empty,
Chain(Vec<LogicalChainOper>),
Condition(Condition),
}
#[derive(Default, Debug, Clone, PartialEq)]
pub struct ConditionHolder {
pub contents: ConditionHolderContents,
}
impl Condition {
#[allow(clippy::should_implement_trait)]
pub fn add<C>(mut self, condition: C) -> Self
where
C: Into<ConditionExpression>,
{
let mut expr: ConditionExpression = condition.into();
if let ConditionExpression::Condition(ref mut c) = expr {
if c.conditions.len() == 1 && !c.negate {
expr = c.conditions.pop().unwrap();
}
}
self.conditions.push(expr);
self
}
#[allow(clippy::should_implement_trait)]
pub fn add_option<C>(self, other: Option<C>) -> Self
where
C: Into<ConditionExpression>,
{
if let Some(other) = other {
self.add(other)
} else {
self
}
}
pub fn any() -> Condition {
Condition {
negate: false,
condition_type: ConditionType::Any,
conditions: Vec::new(),
}
}
pub fn all() -> Condition {
Condition {
negate: false,
condition_type: ConditionType::All,
conditions: Vec::new(),
}
}
#[allow(clippy::should_implement_trait)]
pub fn not(mut self) -> Self {
self.negate = !self.negate;
self
}
pub fn is_empty(&self) -> bool {
self.conditions.is_empty()
}
pub fn len(&self) -> usize {
self.conditions.len()
}
}
impl From<Condition> for SimpleExpr {
fn from(cond: Condition) -> Self {
let mut inner_exprs = vec![];
for ce in cond.conditions {
inner_exprs.push(match ce {
ConditionExpression::Condition(c) => c.into(),
ConditionExpression::SimpleExpr(e) => e,
});
}
let mut inner_exprs_into_iter = inner_exprs.into_iter();
let expr = if let Some(first_expr) = inner_exprs_into_iter.next() {
let mut out_expr = first_expr;
for e in inner_exprs_into_iter {
out_expr = match cond.condition_type {
ConditionType::Any => out_expr.or(e),
ConditionType::All => out_expr.and(e),
};
}
out_expr
} else {
SimpleExpr::Constant(match cond.condition_type {
ConditionType::Any => false.into(),
ConditionType::All => true.into(),
})
};
if cond.negate {
expr.not()
} else {
expr
}
}
}
impl From<ConditionExpression> for SimpleExpr {
fn from(ce: ConditionExpression) -> Self {
match ce {
ConditionExpression::Condition(c) => c.into(),
ConditionExpression::SimpleExpr(e) => e,
}
}
}
impl From<Condition> for ConditionExpression {
fn from(condition: Condition) -> Self {
ConditionExpression::Condition(condition)
}
}
impl From<SimpleExpr> for ConditionExpression {
fn from(condition: SimpleExpr) -> Self {
ConditionExpression::SimpleExpr(condition)
}
}
#[macro_export]
macro_rules! any {
( $( $x:expr ),* $(,)?) => {
{
let mut tmp = $crate::Condition::any();
$(
tmp = tmp.add($x);
)*
tmp
}
};
}
#[macro_export]
macro_rules! all {
( $( $x:expr ),* $(,)?) => {
{
let mut tmp = $crate::Condition::all();
$(
tmp = tmp.add($x);
)*
tmp
}
};
}
pub trait ConditionalStatement {
fn and_where(&mut self, other: SimpleExpr) -> &mut Self {
self.cond_where(other)
}
fn and_where_option(&mut self, other: Option<SimpleExpr>) -> &mut Self {
if let Some(other) = other {
self.and_where(other);
}
self
}
#[doc(hidden)]
fn and_or_where(&mut self, condition: LogicalChainOper) -> &mut Self;
fn cond_where<C>(&mut self, condition: C) -> &mut Self
where
C: IntoCondition;
}
impl IntoCondition for SimpleExpr {
fn into_condition(self) -> Condition {
Condition::all().add(self)
}
}
impl IntoCondition for Condition {
fn into_condition(self) -> Condition {
self
}
}
impl ConditionHolder {
pub fn new() -> Self {
Self::default()
}
pub fn new_with_condition(condition: Condition) -> Self {
let contents = ConditionHolderContents::Condition(condition);
Self { contents }
}
pub fn is_empty(&self) -> bool {
match &self.contents {
ConditionHolderContents::Empty => true,
ConditionHolderContents::Chain(c) => c.is_empty(),
ConditionHolderContents::Condition(c) => c.conditions.is_empty(),
}
}
pub fn is_one(&self) -> bool {
match &self.contents {
ConditionHolderContents::Empty => true,
ConditionHolderContents::Chain(c) => c.len() == 1,
ConditionHolderContents::Condition(c) => c.conditions.len() == 1,
}
}
pub fn add_and_or(&mut self, condition: LogicalChainOper) {
match &mut self.contents {
ConditionHolderContents::Empty => {
self.contents = ConditionHolderContents::Chain(vec![condition])
}
ConditionHolderContents::Chain(c) => c.push(condition),
ConditionHolderContents::Condition(_) => {
panic!("Cannot mix `and_where`/`or_where` and `cond_where` in statements")
}
}
}
pub fn add_condition(&mut self, mut addition: Condition) {
match std::mem::take(&mut self.contents) {
ConditionHolderContents::Empty => {
self.contents = ConditionHolderContents::Condition(addition);
}
ConditionHolderContents::Condition(mut current) => {
if current.condition_type == ConditionType::All && !current.negate {
if addition.condition_type == ConditionType::All && !addition.negate {
current.conditions.append(&mut addition.conditions);
self.contents = ConditionHolderContents::Condition(current);
} else {
self.contents = ConditionHolderContents::Condition(current.add(addition));
}
} else {
self.contents = ConditionHolderContents::Condition(
Condition::all().add(current).add(addition),
);
}
}
ConditionHolderContents::Chain(_) => {
panic!("Cannot mix `and_where`/`or_where` and `cond_where` in statements")
}
}
}
}
#[cfg(test)]
mod test {
use crate::{tests_cfg::*, *};
use pretty_assertions::assert_eq;
#[test]
#[cfg(feature = "backend-mysql")]
fn test_blank_condition() {
let query = Query::select()
.column(Glyph::Image)
.from(Glyph::Table)
.cond_where(Cond::all())
.cond_where(Expr::val(1).eq(1))
.cond_where(Expr::val(2).eq(2))
.cond_where(Cond::any().add(Expr::val(3).eq(3)).add(Expr::val(4).eq(4)))
.to_owned();
assert_eq!(
query.to_string(MysqlQueryBuilder),
"SELECT `image` FROM `glyph` WHERE 1 = 1 AND 2 = 2 AND (3 = 3 OR 4 = 4)"
);
}
}