use crate::dialect::DialectTypes;
use crate::sql::{SQL, Token};
use crate::traits::SQLParam;
use crate::types::BooleanLike;
use super::{AggOr, AggregateKind, Expr, NullOr, Nullability, SQLExpr};
const EMPTY_CONJUNCTION: &str = "TRUE";
const EMPTY_DISJUNCTION: &str = "FALSE";
mod sealed {
pub trait Sealed {}
}
#[doc(hidden)]
#[derive(Debug)]
pub struct ConditionSink<'a, V: SQLParam> {
sql: SQL<'a, V>,
separator: Token,
len: usize,
}
impl<'a, V: SQLParam + 'a> ConditionSink<'a, V> {
fn new(separator: Token) -> Self {
Self {
sql: SQL::empty(),
separator,
len: 0,
}
}
pub fn push(&mut self, condition: Option<SQL<'a, V>>) {
let Some(condition) = condition else { return };
if self.len > 0 {
self.sql.push_mut(self.separator);
}
self.sql.append_mut(condition);
self.len += 1;
}
fn finish(self, empty: &'static str) -> SQL<'a, V> {
if self.len == 0 {
SQL::raw(empty)
} else {
self.sql.parens()
}
}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` is not a list of SQL conditions",
label = "expected a tuple of boolean expressions",
note = "every element must be a boolean-typed expression, or an `Option` of one"
)]
pub trait ConditionList<'a, V: SQLParam>: sealed::Sealed {
type Nullable: Nullability;
type Aggregate: AggregateKind;
#[doc(hidden)]
fn push_conditions(self, sink: &mut ConditionSink<'a, V>);
#[doc(hidden)]
fn push_conditions_ref(&self, sink: &mut ConditionSink<'a, V>);
}
fn combine<'a, V, L>(conditions: L, separator: Token, empty: &'static str) -> SQL<'a, V>
where
V: SQLParam + 'a,
L: ConditionList<'a, V>,
{
let mut sink = ConditionSink::new(separator);
conditions.push_conditions(&mut sink);
sink.finish(empty)
}
fn combine_ref<'a, V, L>(conditions: &L, separator: Token, empty: &'static str) -> SQL<'a, V>
where
V: SQLParam + 'a,
L: ConditionList<'a, V>,
{
let mut sink = ConditionSink::new(separator);
conditions.push_conditions_ref(&mut sink);
sink.finish(empty)
}
#[allow(clippy::type_complexity)]
pub fn all<'a, V, L>(
conditions: L,
) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, L::Nullable, L::Aggregate>
where
V: SQLParam + 'a,
L: ConditionList<'a, V>,
{
SQLExpr::new(combine(conditions, Token::AND, EMPTY_CONJUNCTION))
}
#[allow(clippy::type_complexity)]
pub fn any<'a, V, L>(
conditions: L,
) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, L::Nullable, L::Aggregate>
where
V: SQLParam + 'a,
L: ConditionList<'a, V>,
{
SQLExpr::new(combine(conditions, Token::OR, EMPTY_DISJUNCTION))
}
macro_rules! impl_condition_one {
($T:ident, $i:tt) => {
impl<'a, V, $T> ConditionList<'a, V> for ($T,)
where
V: SQLParam + 'a,
$T: Expr<'a, V>,
<$T as Expr<'a, V>>::SQLType: BooleanLike,
{
type Nullable = <$T as Expr<'a, V>>::Nullable;
type Aggregate = <$T as Expr<'a, V>>::Aggregate;
fn push_conditions(self, sink: &mut ConditionSink<'a, V>) {
sink.push(self.$i.into_condition_sql());
}
fn push_conditions_ref(&self, sink: &mut ConditionSink<'a, V>) {
sink.push(self.$i.to_condition_sql());
}
}
};
}
macro_rules! impl_condition_many {
([$($all:ident),+] [$($i:tt),+] [$($prev:ident),+] $last:ident) => {
impl<'a, V, $($all),+> ConditionList<'a, V> for ($($all,)+)
where
V: SQLParam + 'a,
$($all: Expr<'a, V>,)+
<$last as Expr<'a, V>>::SQLType: BooleanLike,
($($prev,)+): ConditionList<'a, V>,
<($($prev,)+) as ConditionList<'a, V>>::Nullable:
NullOr<<$last as Expr<'a, V>>::Nullable>,
<($($prev,)+) as ConditionList<'a, V>>::Aggregate:
AggOr<<$last as Expr<'a, V>>::Aggregate>,
{
type Nullable = <<($($prev,)+) as ConditionList<'a, V>>::Nullable
as NullOr<<$last as Expr<'a, V>>::Nullable>>::Output;
type Aggregate = <<($($prev,)+) as ConditionList<'a, V>>::Aggregate
as AggOr<<$last as Expr<'a, V>>::Aggregate>>::Output;
fn push_conditions(self, sink: &mut ConditionSink<'a, V>) {
$( sink.push(self.$i.into_condition_sql()); )+
}
fn push_conditions_ref(&self, sink: &mut ConditionSink<'a, V>) {
$( sink.push(self.$i.to_condition_sql()); )+
}
}
};
}
macro_rules! impl_condition_split {
([$A:ident] [$i:tt] [] $only:ident) => {
impl_condition_one!($A, $i);
};
([$($all:ident),+] [$($i:tt),+] [$($prev:ident),+] $last:ident) => {
impl_condition_many!([$($all),+] [$($i),+] [$($prev),+] $last);
};
([$($all:ident),+] [$($i:tt),+] [] $head:ident, $($rest:ident),+) => {
impl_condition_split!([$($all),+] [$($i),+] [$head] $($rest),+);
};
([$($all:ident),+] [$($i:tt),+] [$($prev:ident),+] $head:ident, $($rest:ident),+) => {
impl_condition_split!([$($all),+] [$($i),+] [$($prev),+, $head] $($rest),+);
};
}
macro_rules! impl_condition_expr {
($($T:ident),+) => {
impl<'a, V, $($T),+> Expr<'a, V> for ($($T,)+)
where
V: SQLParam + 'a,
Self: ConditionList<'a, V> + crate::traits::ToSQL<'a, V>,
{
type SQLType = crate::types::Conjunction;
type Nullable = <Self as ConditionList<'a, V>>::Nullable;
type Aggregate = <Self as ConditionList<'a, V>>::Aggregate;
fn to_expr_sql(&self) -> SQL<'a, V> {
combine_ref(self, Token::AND, EMPTY_CONJUNCTION)
}
fn into_expr_sql(self) -> SQL<'a, V> {
combine(self, Token::AND, EMPTY_CONJUNCTION)
}
}
};
}
macro_rules! impl_condition_tuple {
($($T:ident),+; $($i:tt),+) => {
impl<$($T),+> sealed::Sealed for ($($T,)+) {}
impl_condition_split!([$($T),+] [$($i),+] [] $($T),+);
};
}
macro_rules! impl_condition_tuple_expr {
($($T:ident),+; $($i:tt),+) => {
impl_condition_expr!($($T),+);
};
}
with_col_sizes_8!(impl_condition_tuple);
with_col_sizes_8!(impl_condition_tuple_expr);
#[cfg(any(
feature = "col16",
feature = "col32",
feature = "col64",
feature = "col128",
feature = "col200"
))]
with_col_sizes_16!(impl_condition_tuple);