use crate::error::{Result, TransactionError};
use std::mem::ManuallyDrop;
use tokio_postgres::Transaction as PgTransaction;
use tracing::{debug, info};
pub struct Transaction<'a> {
tx: ManuallyDrop<PgTransaction<'a>>,
committed: bool,
}
impl<'a> Transaction<'a> {
pub(crate) fn new(tx: PgTransaction<'a>) -> Self {
Self {
tx: ManuallyDrop::new(tx),
committed: false,
}
}
pub async fn execute(
&self,
query: &str,
params: &[&(dyn tokio_postgres::types::ToSql + Sync)],
) -> Result<u64> {
self.tx.execute(query, params).await.map_err(|e| {
TransactionError::CommitFailed {
message: e.to_string(),
}
.into()
})
}
pub async fn query(
&self,
query: &str,
params: &[&(dyn tokio_postgres::types::ToSql + Sync)],
) -> Result<Vec<tokio_postgres::Row>> {
self.tx.query(query, params).await.map_err(|e| {
TransactionError::CommitFailed {
message: e.to_string(),
}
.into()
})
}
pub async fn savepoint(&self, name: &str) -> Result<()> {
debug!("Creating savepoint: {name}");
self.tx
.execute(&format!("SAVEPOINT {name}"), &[])
.await
.map_err(|e| TransactionError::SavepointError {
message: e.to_string(),
})?;
Ok(())
}
pub async fn release_savepoint(&self, name: &str) -> Result<()> {
debug!("Releasing savepoint: {name}");
self.tx
.execute(&format!("RELEASE SAVEPOINT {name}"), &[])
.await
.map_err(|e| TransactionError::SavepointError {
message: e.to_string(),
})?;
Ok(())
}
pub async fn rollback_to_savepoint(&self, name: &str) -> Result<()> {
debug!("Rolling back to savepoint: {name}");
self.tx
.execute(&format!("ROLLBACK TO SAVEPOINT {name}"), &[])
.await
.map_err(|e| TransactionError::SavepointError {
message: e.to_string(),
})?;
Ok(())
}
pub async fn commit(mut self) -> Result<()> {
info!("Committing transaction");
let tx = unsafe { ManuallyDrop::take(&mut self.tx) };
tx.commit()
.await
.map_err(|e| TransactionError::CommitFailed {
message: e.to_string(),
})?;
self.committed = true;
Ok(())
}
pub async fn rollback(mut self) -> Result<()> {
info!("Rolling back transaction");
let tx = unsafe { ManuallyDrop::take(&mut self.tx) };
tx.rollback().await.map_err(|e| {
TransactionError::RollbackFailed {
message: e.to_string(),
}
.into()
})
}
}
impl<'a> Drop for Transaction<'a> {
fn drop(&mut self) {
if !self.committed {
debug!("Transaction dropped without commit - will auto-rollback");
}
}
}
pub trait TransactionManager {
async fn begin_transaction(&self) -> Result<Transaction<'_>>;
}
#[cfg(test)]
mod tests {
#[test]
fn test_transaction_creation() {
let _placeholder = 1;
}
}