1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
//! Transaction management
//!
//! For general information and examples, see
//! [Transaction control](https://www.tarantool.io/en/doc/latest/book/box/atomic_index/#atomic-atomic-execution).
//!
//! Observe the following rules when working with transactions:
//!
//! 👉 **Rule #1**
//! The requests in a transaction must be sent to a server as a single block.
//! It is not enough to enclose them between begin and commit or rollback.
//! To ensure they are sent as a single block: put them in a function, or put them all on one line, or use a delimiter
//! so that multi-line requests are handled together.
//!
//! 👉 **Rule #2**
//! All database operations in a transaction should use the same storage engine.
//! It is not safe to access tuple sets that are defined with `{engine='vinyl'}` and also access tuple sets that are
//! defined with `{engine='memtx'}`, in the same transaction.
//!
//! 👉 **Rule #3**
//! Requests which cause changes to the data definition – create, alter, drop, truncate – are only allowed with
//! Tarantool version 2.1 or later. Data-definition requests which change an index or change a format, such as
//! `space_object:create_index()` and `space_object:format()`, are not allowed inside transactions except as the first
//! request.
//!
//! See also:
//! - [Transaction control](https://www.tarantool.io/en/doc/latest/book/box/atomic/)
//! - [Lua reference: Functions for transaction management](https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_txn_management/)
//! - [C API reference: Module txn](https://www.tarantool.io/en/doc/latest/dev_guide/reference_capi/txn/)
use crate::error::TarantoolError;
use crate::ffi::tarantool as ffi;
/// Transaction-related error cases
#[derive(Debug, thiserror::Error)]
pub enum TransactionError<E> {
#[error("transaction has already been started")]
AlreadyStarted,
#[error("failed to commit: {0}")]
FailedToCommit(TarantoolError),
#[error("failed to rollback: {0}")]
FailedToRollback(TarantoolError),
#[error("transaction rolled-back: {0}")]
RolledBack(E),
}
/// Executes a transaction in the current fiber.
///
/// A transaction is attached to caller fiber, therefore one fiber can have
/// only one active transaction.
///
/// - `f` - function will be invoked within transaction
///
/// Returns result of function `f` execution. Depending on the function result:
/// - will **commit** - if function completes successfully
/// - will **rollback** - if function completes with any error
pub fn transaction<T, E, F>(f: F) -> Result<T, TransactionError<E>>
where
F: FnOnce() -> Result<T, E>,
{
if unsafe { ffi::box_txn_begin() } < 0 {
return Err(TransactionError::AlreadyStarted);
}
let result = f();
match &result {
Ok(_) => {
if unsafe { ffi::box_txn_commit() } < 0 {
let error = TarantoolError::last();
return Err(TransactionError::FailedToCommit(error));
}
}
Err(_) => {
if unsafe { ffi::box_txn_rollback() } < 0 {
let error = TarantoolError::last();
return Err(TransactionError::FailedToRollback(error));
}
}
}
result.map_err(TransactionError::RolledBack)
}
/// Returns `true` if there's an active transaction.
#[inline(always)]
pub fn is_in_transaction() -> bool {
unsafe { ffi::box_txn() }
}
/// Begin a transaction in the current fiber.
///
/// One fiber can have at most one active transaction.
///
/// Returns an error if there's already an active transcation.
/// May return an error in other cases.
///
/// **NOTE:** it is the caller's responsibility to call [`commit`] or
/// [`rollback`]. Consider using [`transaction`] instead.
#[inline(always)]
pub fn begin() -> Result<(), TarantoolError> {
if unsafe { ffi::box_txn_begin() } < 0 {
return Err(TarantoolError::last());
}
Ok(())
}
/// Commit the active transaction.
///
/// Returns `Ok(())` if there is no active transaction.
///
/// Returns an error in case of IO failure.
/// May return an error in other cases.
#[inline(always)]
pub fn commit() -> Result<(), TarantoolError> {
if unsafe { ffi::box_txn_commit() } < 0 {
return Err(TarantoolError::last());
}
Ok(())
}
/// Rollback the active transaction.
///
/// Returns `Ok(())` if there is no active transaction.
///
/// Returns an error if called from a nested statement, e.g. when called via a trigger.
/// May return an error in other cases.
#[inline(always)]
pub fn rollback() -> Result<(), TarantoolError> {
if unsafe { ffi::box_txn_rollback() } < 0 {
return Err(TarantoolError::last());
}
Ok(())
}