use std::cell::RefCell;
use std::fmt::{self, Debug, Formatter};
use std::future::{self, Future};
use std::rc::Rc;
use sqlx_core::error::Error;
use sqlx_core::sql_str::SqlStr;
use sqlx_core::transaction::TransactionManager;
use worker::send::SendFuture;
use crate::error::unsupported;
use crate::{Do, DoConnection};
const NO_TRANSACTIONS: &str = "Durable Object storage cannot begin a transaction here; \
run the statements inside `DoConnection::transaction`, which takes them as a callback";
impl DoConnection {
pub fn transaction<F, Fut, T, E>(
&self,
callback: F,
) -> impl Future<Output = Result<T, E>> + Send + '_
where
F: FnOnce(DoTransaction) -> Fut + 'static,
Fut: Future<Output = Result<T, E>> + 'static,
T: 'static,
E: From<Error> + 'static,
{
let tx = DoTransaction {
sql: self.sql.clone(),
};
SendFuture::new(async move {
let outcome = Rc::new(RefCell::new(None));
let slot = Rc::clone(&outcome);
let committed = self
.storage
.transaction(move |_| async move {
let result = callback(tx).await;
let failed = result.is_err();
*slot.borrow_mut() = Some(result);
if failed {
Err(worker::Error::RustError("the callback failed".into()))
} else {
Ok(())
}
})
.await;
let outcome = outcome.borrow_mut().take();
match (committed, outcome) {
(_, Some(Err(error))) => Err(error),
(Ok(()), Some(Ok(value))) => Ok(value),
(Err(error), _) => Err(E::from(Error::Protocol(format!(
"the Durable Object storage transaction failed: {error}"
)))),
(Ok(()), None) => Err(E::from(Error::Protocol(
"the Durable Object storage transaction never ran its callback".into(),
))),
}
})
}
}
pub struct DoTransaction {
pub(crate) sql: worker::SqlStorage,
}
impl Debug for DoTransaction {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("DoTransaction").finish_non_exhaustive()
}
}
#[derive(Debug)]
pub struct DoTransactionManager;
impl TransactionManager for DoTransactionManager {
type Database = Do;
fn begin(
_conn: &mut DoConnection,
_statement: Option<SqlStr>,
) -> impl Future<Output = Result<(), Error>> + Send + '_ {
future::ready(Err(unsupported(NO_TRANSACTIONS)))
}
fn commit(_conn: &mut DoConnection) -> impl Future<Output = Result<(), Error>> + Send + '_ {
future::ready(Err(unsupported(NO_TRANSACTIONS)))
}
fn rollback(_conn: &mut DoConnection) -> impl Future<Output = Result<(), Error>> + Send + '_ {
future::ready(Err(unsupported(NO_TRANSACTIONS)))
}
fn start_rollback(_conn: &mut DoConnection) {}
fn get_transaction_depth(_conn: &DoConnection) -> usize {
0
}
}