use snafu::{ResultExt, Snafu};
use sqlx::{postgres::PgPoolOptions, Executor, Pool, Postgres};
use crate::{Error, Result};
pub type Db = Pool<Postgres>;
pub async fn new_db_pool(db_connect_url: &str, max_connections: u32) -> Result<Db> {
PgPoolOptions::new()
.max_connections(max_connections)
.connect(db_connect_url)
.await
.map_err(|e| Error::FailedToCreateDBPool {
message: e.to_string(),
})
}
#[derive(Debug, Snafu)]
pub enum DbModelManagerError {
#[snafu(display("Error checking DB connectivity: "))]
Connectivity { source: sqlx::Error },
#[snafu(display("Error starting DB transaction: "))]
TransactionInit { source: sqlx::Error },
#[snafu(display("Error committing DB transaction: "))]
TransactionCommit { source: sqlx::Error },
#[snafu(display("Error rolling back DB transaction: "))]
TransactionRollback { source: sqlx::Error },
}
impl DbModelManagerError {
pub fn source(&self) -> &sqlx::Error {
match self {
DbModelManagerError::Connectivity { source } => source,
DbModelManagerError::TransactionInit { source } => source,
DbModelManagerError::TransactionCommit { source } => source,
DbModelManagerError::TransactionRollback { source } => source,
}
}
}
#[derive(Debug, Clone)]
pub struct DbModelManager {
db: Db,
}
impl DbModelManager {
pub async fn new(db_connect_url: &str, max_connections: u32) -> Result<Self> {
let db = new_db_pool(db_connect_url, max_connections).await?;
Ok(DbModelManager { db })
}
pub fn new_from_pool(pool: Db) -> Self {
pool.into()
}
pub async fn check_db_connectivity(&self) -> Result<()> {
sqlx::query("SELECT 1")
.execute(self.db())
.await
.context(ConnectivitySnafu)?;
Ok(())
}
pub async fn begin(&self) -> Result<Transaction<'_>> {
let mut raw = self.db().begin().await.context(TransactionInitSnafu)?;
raw.execute("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;")
.await
.context(TransactionInitSnafu)?;
Ok(Transaction(raw))
}
pub fn db(&self) -> &Db {
&self.db
}
}
impl From<Db> for DbModelManager {
fn from(db: Db) -> Self {
Self { db }
}
}
pub struct Transaction<'a>(sqlx::Transaction<'a, Postgres>);
impl Transaction<'_> {
pub(crate) fn executor(&mut self) -> impl Executor<'_, Database = Postgres> {
&mut *self.0
}
pub async fn commit(self) -> Result<()> {
Ok(self.0.commit().await.context(TransactionCommitSnafu)?)
}
pub async fn rollback(self) -> Result<()> {
Ok(self.0.rollback().await.context(TransactionRollbackSnafu)?)
}
}
pub trait AsExecutor: private::ActualExecutor {
fn is_transaction(&self) -> bool {
false
}
}
pub(crate) mod private {
use sqlx::{Executor, Postgres};
pub trait ActualExecutor {
fn as_executor(&mut self) -> impl Executor<'_, Database = Postgres>;
}
}
impl private::ActualExecutor for Transaction<'_> {
fn as_executor(&mut self) -> impl Executor<'_, Database = Postgres> {
self.executor()
}
}
impl AsExecutor for Transaction<'_> {
fn is_transaction(&self) -> bool {
true
}
}
impl private::ActualExecutor for DbModelManager {
fn as_executor(&mut self) -> impl Executor<'_, Database = Postgres> {
self.db()
}
}
impl AsExecutor for DbModelManager {}