diesel-softdelete 0.1.0

Soft-delete support for the Diesel ORM
Documentation
/**
 * 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)); };
}