use crate::error::{EFError, EFResult};
use crate::provider::{IAsyncConnection, IsolationLevel};
use crate::session::{Session, SessionConnection, SharedTxn};
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
pub trait ITransaction: Send + Sync {
fn commit(self: Box<Self>) -> Pin<Box<dyn Future<Output = EFResult<()>> + Send + 'static>>;
fn rollback(self: Box<Self>) -> Pin<Box<dyn Future<Output = EFResult<()>> + Send + 'static>>;
fn create_point<'a>(
&'a mut self,
name: &'a str,
) -> Pin<Box<dyn Future<Output = EFResult<()>> + Send + 'a>>;
fn release_point<'a>(
&'a mut self,
name: &'a str,
) -> Pin<Box<dyn Future<Output = EFResult<()>> + Send + 'a>>;
fn rollback_point<'a>(
&'a mut self,
name: &'a str,
) -> Pin<Box<dyn Future<Output = EFResult<()>> + Send + 'a>>;
fn set_isolation<'a>(
&'a mut self,
level: IsolationLevel,
) -> Pin<Box<dyn Future<Output = EFResult<()>> + Send + 'a>>;
fn connection(&mut self) -> &mut (dyn IAsyncConnection + Send);
}
pub(crate) struct ConnectionGuard {
conn: Option<Box<dyn IAsyncConnection>>,
}
impl ConnectionGuard {
pub(crate) fn new(conn: Box<dyn IAsyncConnection>) -> Self {
Self { conn: Some(conn) }
}
pub(crate) fn as_mut(&mut self) -> &mut dyn IAsyncConnection {
self.conn
.as_mut()
.expect("connection guard already disarmed")
.as_mut()
}
pub(crate) fn disarm(mut self) -> Box<dyn IAsyncConnection> {
self.conn.take().expect("connection guard already disarmed")
}
}
impl Drop for ConnectionGuard {
fn drop(&mut self) {
if let Some(conn) = self.conn.take() {
conn.discard();
}
}
}
struct TxnState {
shared: Arc<SharedTxn>,
facade: SessionConnection,
}
pub struct DbTransaction {
state: Option<TxnState>,
session: Arc<Session>,
runtime: Option<tokio::runtime::Handle>,
}
impl DbTransaction {
pub(crate) fn new_shared(
shared: Arc<SharedTxn>,
facade: SessionConnection,
session: Arc<Session>,
) -> Self {
Self {
state: Some(TxnState { shared, facade }),
session,
runtime: tokio::runtime::Handle::try_current().ok(),
}
}
}
impl ITransaction for DbTransaction {
fn commit(self: Box<Self>) -> Pin<Box<dyn Future<Output = EFResult<()>> + Send + 'static>> {
Box::pin(async move {
let mut this = self;
let Some(state) = this.state.take() else {
return Err(EFError::transaction("transaction already consumed"));
};
let result = match state.shared.take().await {
Ok(conn) => {
let mut guard = ConnectionGuard::new(conn);
match guard.as_mut().commit_transaction().await {
Ok(()) => {
drop(guard.disarm());
Ok(())
}
Err(e) => {
let _ = guard.as_mut().rollback_transaction().await;
Err(e)
}
}
}
Err(e) => Err(e),
};
this.session.clear().await;
result
})
}
fn rollback(self: Box<Self>) -> Pin<Box<dyn Future<Output = EFResult<()>> + Send + 'static>> {
Box::pin(async move {
let mut this = self;
let Some(state) = this.state.take() else {
return Err(EFError::transaction("transaction already consumed"));
};
let result = match state.shared.take().await {
Ok(conn) => {
let mut guard = ConnectionGuard::new(conn);
match guard.as_mut().rollback_transaction().await {
Ok(()) => {
drop(guard.disarm());
Ok(())
}
Err(e) => Err(e),
}
}
Err(e) => Err(e),
};
this.session.clear().await;
result
})
}
fn create_point<'a>(
&'a mut self,
name: &'a str,
) -> Pin<Box<dyn Future<Output = EFResult<()>> + Send + 'a>> {
Box::pin(async move {
self.state
.as_mut()
.ok_or_else(|| EFError::transaction("transaction already consumed"))?
.facade
.create_savepoint(name)
.await
})
}
fn release_point<'a>(
&'a mut self,
name: &'a str,
) -> Pin<Box<dyn Future<Output = EFResult<()>> + Send + 'a>> {
Box::pin(async move {
self.state
.as_mut()
.ok_or_else(|| EFError::transaction("transaction already consumed"))?
.facade
.release_savepoint(name)
.await
})
}
fn rollback_point<'a>(
&'a mut self,
name: &'a str,
) -> Pin<Box<dyn Future<Output = EFResult<()>> + Send + 'a>> {
Box::pin(async move {
self.state
.as_mut()
.ok_or_else(|| EFError::transaction("transaction already consumed"))?
.facade
.rollback_to_savepoint(name)
.await
})
}
fn set_isolation<'a>(
&'a mut self,
level: IsolationLevel,
) -> Pin<Box<dyn Future<Output = EFResult<()>> + Send + 'a>> {
Box::pin(async move {
self.state
.as_mut()
.ok_or_else(|| EFError::transaction("transaction already consumed"))?
.facade
.set_transaction_isolation(level)
.await
})
}
fn connection(&mut self) -> &mut (dyn IAsyncConnection + Send) {
&mut self
.state
.as_mut()
.expect("transaction already consumed")
.facade
}
}
impl Drop for DbTransaction {
fn drop(&mut self) {
let state = self.state.take();
self.session.clear_now();
let Some(state) = state else {
return;
};
if let Some(conn) = state.shared.try_take() {
conn.discard();
return;
}
let handle = self
.runtime
.clone()
.or_else(|| tokio::runtime::Handle::try_current().ok());
let Some(handle) = handle else {
return;
};
let session = Arc::clone(&self.session);
handle.spawn(async move {
if let Ok(conn) = state.shared.take().await {
let mut guard = ConnectionGuard::new(conn);
let _ = guard.as_mut().rollback_transaction().await;
}
session.clear().await;
});
}
}