/**
* Soft-delete implementation and utils for diesel
*/
use diesel::{
associations::HasTable,
dsl::*,
expression::NonAggregate,
query_builder::AsQuery,
query_dsl::{methods::{FilterDsl, FindDsl}, InternalJoinDsl},
query_source::joins::{Inner, LeftOuter},
sql_types::Bool,
BoolExpressionMethods, Expression, JoinTo,
SelectableExpression,
};
type Not<T> = diesel::helper_types::not<T>;
/// A SQL database table that makes use of Soft Delete.
pub trait SoftDelete: Sized {
/// The type returned by `deleted_col`
type Deleted: SelectableExpression<Self> + NonAggregate + Expression<SqlType = Bool>;
/// The type returned by `deleted_at_col`
type DeletedAt: SelectableExpression<Self> + NonAggregate;
fn deleted_col(&self) -> Self::Deleted;
fn deleted_at_col(&self) -> Self::DeletedAt;
}
/// The `soft_find` method.
pub trait SoftFindDsl<PK>: SoftDelete {
type Output;
fn soft_find(self, id: PK) -> Self::Output;
}
impl<T, PK> SoftFindDsl<PK> for T
where
T: SoftDelete + FindDsl<PK>,
<T as FindDsl<PK>>::Output: FilterDsl<Not<Self::Deleted>>,
{
type Output = Filter<<T as FindDsl<PK>>::Output, Not<T::Deleted>>;
fn soft_find(self, id: PK) -> Self::Output {
let deleted = self.deleted_col();
self.find(id).filter(not(deleted))
}
}
/// Indicates that two tables can be joined without an explicit `ON` clause while respecting
/// soft-delete.
pub trait SoftJoinTo<T>: JoinTo<T> {
type SoftOnClause;
fn soft_join_target(rhs: T) -> (<Self as JoinTo<T>>::FromClause, Self::SoftOnClause);
}
impl<Lhs, Rhs> SoftJoinTo<Rhs> for Lhs
where
Lhs: JoinTo<Rhs>,
Rhs: SoftDelete + HasTable<Table = Rhs>,
<Lhs as JoinTo<Rhs>>::OnClause: Expression + BoolExpressionMethods,
{
type SoftOnClause = And<Lhs::OnClause, Not<Rhs::Deleted>>;
fn soft_join_target(rhs: Rhs) -> (Self::FromClause, Self::SoftOnClause) {
let (rhs, on_clause) = Self::join_target(rhs);
(rhs, on_clause.and(not(Rhs::deleted_col(&Rhs::table()))))
}
}
pub trait SoftJoin<Rhs, Kind> {
type Output: AsQuery;
fn soft_join(self, rhs: Rhs, kind: Kind) -> Self::Output;
}
impl<Lhs, Rhs, Kind> SoftJoin<Rhs, Kind> for Lhs
where
Lhs: SoftJoinTo<Rhs>,
Lhs: InternalJoinDsl<<Lhs as JoinTo<Rhs>>::FromClause, Kind, <Lhs as SoftJoinTo<Rhs>>::SoftOnClause>,
{
type Output = <Lhs as InternalJoinDsl<Lhs::FromClause, Kind, Lhs::SoftOnClause>>::Output;
fn soft_join(self, rhs: Rhs, kind: Kind) -> Self::Output {
let (from, on) = Lhs::soft_join_target(rhs);
self.join(from, kind, on)
}
}
pub trait SoftJoinDsl: Sized {
fn soft_inner_join<Rhs>(self, rhs: Rhs) -> Self::Output
where
Self: SoftJoin<Rhs, Inner>,
{
self.soft_join(rhs, Inner)
}
fn soft_left_join<Rhs>(self, rhs: Rhs) -> Self::Output
where
Self: SoftJoin<Rhs, LeftOuter>,
{
self.soft_join(rhs, LeftOuter)
}
}
impl<Lhs> SoftJoinDsl for Lhs where Lhs: Sized {}
#[macro_export]
macro_rules! soft_del {
($table:path => ($deleted:path, $deleted_at:path)) => {
impl $crate::soft_delete::SoftDelete for $table {
type Deleted = $deleted;
type DeletedAt = $deleted_at;
fn deleted_col(&self) -> Self::Deleted { $deleted }
fn deleted_at_col(&self) -> Self::DeletedAt { $deleted_at }
}
impl<Left, Right, Kind> $crate::soft_delete::SoftJoinTo<Join<Left, Right, Kind>> for $table
where
Join<Left, Right, Kind>: SoftJoinTo<$table>,
{
type SoftOnClause = <Join<Left, Right, Kind> as SoftJoinTo<$table>>::SoftOnClause;
fn soft_join_target(rhs: Join<Left, Right, Kind>) -> (Self::FromClause, Self::SoftOnClause) {
let (_, on_clause) = Join::soft_join_target($table);
(rhs, on_clause)
}
}
impl<Join, On> SoftJoinTo<JoinOn<Join, On>> for $table
where
JoinOn<Join, On>: SoftJoinTo<$table>,
{
type SoftOnClause = <JoinOn<Join, On> as SoftJoinTo<$table>>::SoftOnClause;
fn soft_join_target(rhs: JoinOn<Join, On>) -> (Self::FromClause, Self::SoftOnClause) {
let (_, on_clause) = JoinOn::soft_join_target($table);
(rhs, on_clause)
}
}
impl<F, S, D, W, O, L, Of, G>
$crate::query_source::SoftJoinTo<SelectStatement<F, S, D, W, O, L, Of, G>> for $table
where
SelectStatement<F, S, D, W, O, L, Of, G>: SoftJoinTo<$table>,
{
type SoftOnClause = <
SelectStatement<F, S, D, W, O, L, Of, G> as SoftJoinTo<$table>
>::SoftOnClause;
fn soft_join_target(
rhs: SelectStatement<F, S, D, W, O, L, Of, G>,
) -> (Self::FromClause, Self::SoftOnClause) {
let (_, on_clause) = SelectStatement::soft_join_target($table);
(rhs, on_clause)
}
}
//impl<'a, QS, ST, DB> SoftJoinTo<BoxedSelectStatement<'a, QS, ST, DB>> for $table
//where
// BoxedSelectStatement<'a, QS, ST, DB>: SoftJoinTo<$table>,
//{
// type SoftOnClause = <BoxedSelectStatement<'a, QS, ST, DB> as SoftJoinTo<$table>>::SoftOnClause;
// fn soft_join_target(
// rhs: BoxedSelectStatement<'a, QS, ST, DB>,
// ) -> (Self::FromClause, Self::SoftOnClause) {
// let (_, on_clause) = BoxedSelectStatement::soft_join_target($table);
// (rhs, on_clause)
// }
//}
};
($table:ident) => { soft_del!($table::table => ($table::deleted, $table::deletion_date)); };
}