mem-query 0.0.1

Relational algebra interface for Rust collections
use std::error::Error;
use either::Either;

/// An action required to revert a failed transaction
pub trait UndoLog<Root:?Sized> {
    fn revert(self, root:&mut Root);
}

/// A fallible operation that can be reverted if it succeeds
pub trait RevertableOp<Root:?Sized> {
    type Err: Error;
    type Log: UndoLog<Root>;
    
    fn apply(self, root: &mut Root)->Result<Self::Log, Self::Err>;
}

/// A function that turns one mutable reference into another one
pub trait Accessor<Src:?Sized> {
    type Dest: ?Sized;
    fn access<F,O>(&self, src: &mut Src, op:F)->O
        where F: FnOnce(&mut Self::Dest)->O;
}

impl<Src, Dest, G, Ptr> Accessor<Src> for G
    where Src: ?Sized,
          Dest:?Sized,
          G: Fn(&mut Src)->Ptr,
          Ptr: std::ops::DerefMut<Target = Dest> {
    type Dest = Dest;
    #[inline(always)]
    fn access<F,O>(&self, src: &mut Src, op:F)->O
        where F: FnOnce(&mut Self::Dest)->O
    {
        let mut ptr = self(src);
        op(&mut *ptr)
    }
}

/// The empty tuple is a No-op undo for everthing
impl<Root:?Sized> UndoLog<Root> for () {
    fn revert(self, _:&mut Root) {}
}

/// A tuple of two logs (A,B) first reverts log A and then log B
impl<Root:?Sized, A: UndoLog<Root>, B: UndoLog<Root>> UndoLog<Root> for (A,B) {
    fn revert(self, root:&mut Root) {
        self.0.revert(root);
        self.1.revert(root);
    }
}

impl<Root:?Sized, T: UndoLog<Root>> UndoLog<Root> for Option<T> {
    fn revert(self, root:&mut Root) {
        if let Some(undo) = self {
            undo.revert(root);
        }
    }
}

impl<Root:?Sized, A: UndoLog<Root>, B:UndoLog<Root>> UndoLog<Root> for Either<A,B> {
    fn revert(self, root:&mut Root) {
        match self {
            Either::Left(left) => left.revert(root),
            Either::Right(right) => right.revert(root)
        };
    }
}

/// A vector of logs is reverted in reverse order: `push`ing operation logs
/// onto the end of the vector as they are applied.
impl<Root:?Sized, T: UndoLog<Root>> UndoLog<Root> for Vec<T> {
    fn revert(self, root:&mut Root) {
        self.into_iter().rev().for_each(|log| log.revert(root));
    }
}

/// An undo log for some object that's accessible via the main root.
/// The `AccessFn` will find the object again before using the `Log` to revert
/// We can't store away a raw pointer because subsequent operations in the
/// transaction might move the subroot (via `Vec` re-allocation, for example)
pub struct SubUndoLog<AccessFn, Log, SubRoot:?Sized>(AccessFn, Log, std::marker::PhantomData<SubRoot>);

impl<Root:?Sized, AccessFn, Log, SubRoot> UndoLog<Root> for SubUndoLog<AccessFn, Log, SubRoot>
where AccessFn: Accessor<Root, Dest=SubRoot>, Log: UndoLog<SubRoot>, SubRoot:?Sized {
    fn revert(self, root: &mut Root) {
        let SubUndoLog(access, log, _) = self;
        access.access(root, |subroot| log.revert(subroot));
    }
}

/// A transaction holds exclusive access to some root object and lets you
/// apply `RevertableOp`s to it.  If any of these operations fail, the prior
/// ones are immediately reverted and further operation requests are ignored.
/// The error will be returned from `commit()`.
#[must_use]
pub struct Transaction<'a, Root:'a+?Sized, Undo=(), E=std::convert::Infallible> {
    root: &'a mut Root,
    status: Status<Undo, E>,
}

pub enum Status<Undo, E> {
    Poisoned(E),
    Active(Undo),
}

impl<'a, T:'a+?Sized> Transaction<'a, T> {
    /// Start a new transaction protecting the object `root`
    pub fn start(root:&'a mut T)->Self {
        Transaction { root, status: Status::Active(()) }
    }
}

impl<'a, Root:?Sized+'a, Undo: UndoLog<Root>, E> Transaction<'a, Root, Undo, E> {
    /// Discard the undo log, and return any error that occurred.
    pub fn commit(self)->Result<(), E> {
        match self.status {
            Status::Poisoned(err) => Err(err),
            Status::Active(_) => Ok(())
        }
    }
    
    /// Process the undo log to return the root to its original state.
    /// Discards errors.
    pub fn revert(mut self) {
        if let Status::Active(undo) = self.status { undo.revert(&mut self.root) }
    }
    
/*
    /// Mark this transaction as failed and process the undo log immediately.
    pub fn poison(&mut self, err: Box<dyn Error>) {
        match std::mem::replace(&mut self.status, Status::Poisoned(err)) {
            Status::Active(undo) => { undo.revert(&mut self.root); }
            Status::Poisoned(err) => { self.status = Status::Poisoned(err); }
        }
    }
  
*/
  
    /// Run a transaction on an object accessible from the `root`.
    pub fn subtransaction<SubRoot, SubUndo, AccessFn, BodyFn, SubErr>(self, access: AccessFn, body: BodyFn)->
        Transaction<'a, Root, (SubUndoLog<AccessFn, SubUndo, SubRoot>, Undo), Either<SubErr, E>>
    where
        AccessFn: Accessor<Root, Dest=SubRoot>,
        SubRoot: ?Sized,
        BodyFn: FnOnce(Transaction<'_, SubRoot, ()>)->Transaction<'_, SubRoot, SubUndo, SubErr>,
        SubUndo: UndoLog<SubRoot>
    {
        match self.status {
            Status::Poisoned(e) => Transaction { root: self.root, status: Status::Poisoned(Either::Right(e)) },
            Status::Active(undo) => {
                let status = access.access(self.root, |subroot| {
                    body(Transaction { root: subroot, status: Status::Active(())}).status
                });
                match status {
                    Status::Poisoned(e) => Transaction { root: self.root, status: Status::Poisoned(Either::Left(e)) },
                    Status::Active(subundo) => Transaction {
                        root: self.root,
                        status: Status::Active( (SubUndoLog(access, subundo, std::marker::PhantomData), undo) )
                    }
                }
            }
        } 
    }
    
    /// Apply a `RevertableOp`
    pub fn apply<Op: RevertableOp<Root>>(self, op:Op)->Transaction<'a, Root, (Op::Log, Undo), Either<Op::Err, E>> {
        match self.status {
            Status::Poisoned(e) => Transaction { root: self.root, status: Status::Poisoned(Either::Right(e)) },
            Status::Active(undo) => match op.apply(self.root) {
                Ok(log) => Transaction { root: self.root, status: Status::Active((log, undo)) },
                Err(e) => {
                    undo.revert(self.root);
                    Transaction { root: self.root, status: Status::Poisoned(Either::Left(e)) }
                }
            }
        }
    }
    
    /// Apply several instances of the same `RevertableOp`.
    /// The iterator will stop as soon as one of the operations fails.
    pub fn apply_multi<Op: RevertableOp<Root>>(self, ops:impl IntoIterator<Item=Op>)
    ->Transaction<'a, Root, (Vec<Op::Log>, Undo), Either<Op::Err, E>> {
        match self.status {
            Status::Poisoned(e) => Transaction { root: self.root, status: Status::Poisoned(Either::Right(e)) },
            Status::Active(undo) => {
                let mut logs: Vec<Op::Log> = vec![];
                for op in ops {
                    match op.apply(self.root) {
                        Ok(log) => logs.push(log),
                        Err(e) => {
                            logs.revert(self.root);
                            undo.revert(self.root);
                            return Transaction { root: self.root, status: Status::Poisoned(Either::Left(e)) };
                        }
                    }
                }
                Transaction { root: self.root, status: Status::Active((logs, undo)) }
            }
        }
    }

    /// Allows a `Transaction` to be used within the implementation of a
    /// `RevertableOp`.
    pub fn into_undo_log(self)->Result<Undo, E> {
        match self.status {
            Status::Active(undo) => Ok(undo),
            Status::Poisoned(e) => Err(e)
        }
    }
}

/*
impl<'a, Root:'a+?Sized, Undo> std::ops::Deref for Transaction<'a, Root, Undo> {
    type Target = Root;
    fn deref(&self)->&Root { &self.root }
}
*/