#[cfg(not(feature = "blocking"))]
use std::future::Future;
use arangors_lite::transaction::{Status, Transaction as TransactionLayer};
pub use {
transaction_builder::TransactionBuilder, transaction_connection::TransactionDatabaseConnection,
transaction_output::TransactionOutput,
};
use crate::{DatabaseConnection, Error};
mod transaction_builder;
mod transaction_connection;
mod transaction_output;
#[derive(Debug)]
pub struct Transaction {
accessor: TransactionLayer,
database_connection: TransactionDatabaseConnection,
}
impl Transaction {
#[must_use]
#[inline]
pub fn id(&self) -> &str {
self.accessor.id()
}
#[maybe_async::maybe_async]
pub async fn new(db_connection: &DatabaseConnection) -> Result<Self, Error> {
TransactionBuilder::new().build(db_connection).await
}
#[maybe_async::maybe_async]
pub async fn commit(&self) -> Result<(), Error> {
let status = self.accessor.commit().await?;
log::debug!("Transaction committed with status: {:?}", status);
if !matches!(status, Status::Committed) {
let msg = format!("Unexpected {:?} transaction status after commit", status);
log::error!("{}", msg);
return Err(Error::InternalError { message: Some(msg) });
}
Ok(())
}
#[maybe_async::maybe_async]
pub async fn abort(&self) -> Result<(), Error> {
let status = self.accessor.abort().await?;
log::debug!("Transaction aborted with status: {:?}", status);
if !matches!(status, Status::Aborted) {
let msg = format!("Unexpected {:?} transaction status after abort", status);
log::error!("{}", msg);
return Err(Error::InternalError { message: Some(msg) });
}
Ok(())
}
#[cfg(not(feature = "blocking"))]
pub async fn safe_execute<T, O, F>(&self, operations: O) -> Result<TransactionOutput<T>, Error>
where
O: FnOnce(TransactionDatabaseConnection) -> F,
F: Future<Output = Result<T, Error>>,
{
log::trace!("Safely executing transactional operations..");
let res = operations(self.database_connection.clone()).await;
log::trace!(
"Safely executing transactional operations.. Done. Success: {}",
res.is_ok()
);
self.handle_safe_execute(res).await
}
#[cfg(feature = "blocking")]
pub fn safe_execute<T, O>(&self, operations: O) -> Result<TransactionOutput<T>, Error>
where
O: FnOnce(TransactionDatabaseConnection) -> Result<T, Error>,
{
log::trace!("Safely executing transactional operations..");
let res = operations(self.database_connection.clone());
log::trace!(
"Safely executing transactional operations.. Done. Success: {}",
res.is_ok()
);
self.handle_safe_execute(res)
}
#[maybe_async::maybe_async]
async fn handle_safe_execute<T>(
&self,
result: Result<T, Error>,
) -> Result<TransactionOutput<T>, Error> {
match result {
Ok(value) => {
log::debug!("Transaction succeeded. Committing..");
self.commit().await?;
Ok(TransactionOutput::Committed(value))
}
Err(err) => {
log::debug!("Transaction failed with: {}. Aborting..", err);
self.abort().await?;
Ok(TransactionOutput::Aborted(err))
}
}
}
#[must_use]
#[inline]
pub const fn database_connection(&self) -> &TransactionDatabaseConnection {
&self.database_connection
}
}