use std::ops::RangeBounds; use crate::exid::ExId; use crate::{Automerge, ChangeHash, KeysAt, ObjType, OpObserver, Prop, ScalarValue, Value, Values}; use crate::{AutomergeError, Keys}; use crate::{ListRange, ListRangeAt, MapRange, MapRangeAt}; use super::{CommitOptions, Transactable, TransactionInner}; /// A transaction on a document. /// Transactions group operations into a single change so that no other operations can happen /// in-between. /// /// Created from [`Automerge::transaction`]. /// /// ## Drop /// /// This transaction should be manually committed or rolled back. If not done manually then it will /// be rolled back when it is dropped. This is to prevent the document being in an unsafe /// intermediate state. /// This is consistent with `?` error handling. #[derive(Debug)] pub struct Transaction<'a> { // this is an option so that we can take it during commit and rollback to prevent it being // rolled back during drop. pub(crate) inner: Option, pub(crate) doc: &'a mut Automerge, } impl<'a> Transaction<'a> { /// Get the heads of the document before this transaction was started. pub fn get_heads(&self) -> Vec { self.doc.get_heads() } /// Commit the operations performed in this transaction, returning the hashes corresponding to /// the new heads. pub fn commit(mut self) -> ChangeHash { self.inner .take() .unwrap() .commit::<()>(self.doc, None, None, None) } /// Commit the operations in this transaction with some options. /// /// ``` /// # use automerge::transaction::CommitOptions; /// # use automerge::transaction::Transactable; /// # use automerge::ROOT; /// # use automerge::Automerge; /// # use automerge::ObjType; /// # use std::time::SystemTime; /// let mut doc = Automerge::new(); /// let mut tx = doc.transaction(); /// tx.put_object(ROOT, "todos", ObjType::List).unwrap(); /// let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs() as /// i64; /// tx.commit_with::<()>(CommitOptions::default().with_message("Create todos list").with_time(now)); /// ``` pub fn commit_with(mut self, options: CommitOptions<'_, Obs>) -> ChangeHash { self.inner.take().unwrap().commit( self.doc, options.message, options.time, options.op_observer, ) } /// Undo the operations added in this transaction, returning the number of cancelled /// operations. pub fn rollback(mut self) -> usize { self.inner.take().unwrap().rollback(self.doc) } } impl<'a> Transactable for Transaction<'a> { /// Get the number of pending operations in this transaction. fn pending_ops(&self) -> usize { self.inner.as_ref().unwrap().pending_ops() } /// Set the value of property `P` to value `V` in object `obj`. /// /// # Errors /// /// This will return an error if /// - The object does not exist /// - The key is the wrong type for the object /// - The key does not exist in the object fn put, P: Into, V: Into>( &mut self, obj: O, prop: P, value: V, ) -> Result<(), AutomergeError> { self.inner .as_mut() .unwrap() .put(self.doc, obj.as_ref(), prop, value) } fn put_object, P: Into>( &mut self, obj: O, prop: P, value: ObjType, ) -> Result { self.inner .as_mut() .unwrap() .put_object(self.doc, obj.as_ref(), prop, value) } fn insert, V: Into>( &mut self, obj: O, index: usize, value: V, ) -> Result<(), AutomergeError> { self.inner .as_mut() .unwrap() .insert(self.doc, obj.as_ref(), index, value) } fn insert_object>( &mut self, obj: O, index: usize, value: ObjType, ) -> Result { self.inner .as_mut() .unwrap() .insert_object(self.doc, obj.as_ref(), index, value) } fn increment, P: Into>( &mut self, obj: O, prop: P, value: i64, ) -> Result<(), AutomergeError> { self.inner .as_mut() .unwrap() .increment(self.doc, obj.as_ref(), prop, value) } fn delete, P: Into>( &mut self, obj: O, prop: P, ) -> Result<(), AutomergeError> { self.inner .as_mut() .unwrap() .delete(self.doc, obj.as_ref(), prop) } /// Splice new elements into the given sequence. Returns a vector of the OpIds used to insert /// the new elements fn splice, V: IntoIterator>( &mut self, obj: O, pos: usize, del: usize, vals: V, ) -> Result<(), AutomergeError> { self.inner .as_mut() .unwrap() .splice(self.doc, obj.as_ref(), pos, del, vals) } fn keys>(&self, obj: O) -> Keys<'_, '_> { self.doc.keys(obj) } fn keys_at>(&self, obj: O, heads: &[ChangeHash]) -> KeysAt<'_, '_> { self.doc.keys_at(obj, heads) } fn map_range, R: RangeBounds>( &self, obj: O, range: R, ) -> MapRange<'_, R> { self.doc.map_range(obj, range) } fn map_range_at, R: RangeBounds>( &self, obj: O, range: R, heads: &[ChangeHash], ) -> MapRangeAt<'_, R> { self.doc.map_range_at(obj, range, heads) } fn list_range, R: RangeBounds>( &self, obj: O, range: R, ) -> ListRange<'_, R> { self.doc.list_range(obj, range) } fn list_range_at, R: RangeBounds>( &self, obj: O, range: R, heads: &[ChangeHash], ) -> ListRangeAt<'_, R> { self.doc.list_range_at(obj, range, heads) } fn values>(&self, obj: O) -> Values<'_> { self.doc.values(obj) } fn values_at>(&self, obj: O, heads: &[ChangeHash]) -> Values<'_> { self.doc.values_at(obj, heads) } fn length>(&self, obj: O) -> usize { self.doc.length(obj) } fn length_at>(&self, obj: O, heads: &[ChangeHash]) -> usize { self.doc.length_at(obj, heads) } fn object_type>(&self, obj: O) -> Option { self.doc.object_type(obj) } fn text>(&self, obj: O) -> Result { self.doc.text(obj) } fn text_at>( &self, obj: O, heads: &[ChangeHash], ) -> Result { self.doc.text_at(obj, heads) } fn get, P: Into>( &self, obj: O, prop: P, ) -> Result, ExId)>, AutomergeError> { self.doc.get(obj, prop) } fn get_at, P: Into>( &self, obj: O, prop: P, heads: &[ChangeHash], ) -> Result, ExId)>, AutomergeError> { self.doc.get_at(obj, prop, heads) } fn get_all, P: Into>( &self, obj: O, prop: P, ) -> Result, ExId)>, AutomergeError> { self.doc.get_all(obj, prop) } fn get_all_at, P: Into>( &self, obj: O, prop: P, heads: &[ChangeHash], ) -> Result, ExId)>, AutomergeError> { self.doc.get_all_at(obj, prop, heads) } fn parents>(&self, obj: O) -> Result, AutomergeError> { self.doc.parents(obj) } } // If a transaction is not commited or rolled back manually then it can leave the document in an // intermediate state. // This defaults to rolling back the transaction to be compatible with `?` error returning before // reaching a call to `commit`. impl<'a> Drop for Transaction<'a> { fn drop(&mut self) { if let Some(txn) = self.inner.take() { txn.rollback(self.doc); } } }