/** * 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 = diesel::helper_types::not; /// A SQL database table that makes use of Soft Delete. pub trait SoftDelete: Sized { /// The type returned by `deleted_col` type Deleted: SelectableExpression + NonAggregate + Expression; /// The type returned by `deleted_at_col` type DeletedAt: SelectableExpression + NonAggregate; fn deleted_col(&self) -> Self::Deleted; fn deleted_at_col(&self) -> Self::DeletedAt; } /// The `soft_find` method. pub trait SoftFindDsl: SoftDelete { type Output; fn soft_find(self, id: PK) -> Self::Output; } impl SoftFindDsl for T where T: SoftDelete + FindDsl, >::Output: FilterDsl>, { type Output = Filter<>::Output, Not>; 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: JoinTo { type SoftOnClause; fn soft_join_target(rhs: T) -> (>::FromClause, Self::SoftOnClause); } impl SoftJoinTo for Lhs where Lhs: JoinTo, Rhs: SoftDelete + HasTable, >::OnClause: Expression + BoolExpressionMethods, { type SoftOnClause = And>; 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 { type Output: AsQuery; fn soft_join(self, rhs: Rhs, kind: Kind) -> Self::Output; } impl SoftJoin for Lhs where Lhs: SoftJoinTo, Lhs: InternalJoinDsl<>::FromClause, Kind, >::SoftOnClause>, { type Output = >::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(self, rhs: Rhs) -> Self::Output where Self: SoftJoin, { self.soft_join(rhs, Inner) } fn soft_left_join(self, rhs: Rhs) -> Self::Output where Self: SoftJoin, { self.soft_join(rhs, LeftOuter) } } impl 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 $crate::soft_delete::SoftJoinTo> for $table where Join: SoftJoinTo<$table>, { type SoftOnClause = as SoftJoinTo<$table>>::SoftOnClause; fn soft_join_target(rhs: Join) -> (Self::FromClause, Self::SoftOnClause) { let (_, on_clause) = Join::soft_join_target($table); (rhs, on_clause) } } impl SoftJoinTo> for $table where JoinOn: SoftJoinTo<$table>, { type SoftOnClause = as SoftJoinTo<$table>>::SoftOnClause; fn soft_join_target(rhs: JoinOn) -> (Self::FromClause, Self::SoftOnClause) { let (_, on_clause) = JoinOn::soft_join_target($table); (rhs, on_clause) } } impl $crate::query_source::SoftJoinTo> for $table where SelectStatement: SoftJoinTo<$table>, { type SoftOnClause = < SelectStatement as SoftJoinTo<$table> >::SoftOnClause; fn soft_join_target( rhs: SelectStatement, ) -> (Self::FromClause, Self::SoftOnClause) { let (_, on_clause) = SelectStatement::soft_join_target($table); (rhs, on_clause) } } //impl<'a, QS, ST, DB> SoftJoinTo> for $table //where // BoxedSelectStatement<'a, QS, ST, DB>: SoftJoinTo<$table>, //{ // type SoftOnClause = 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)); }; }