use super::repositories::*;
use super::{
error::{Result, invalid},
migration::Migrator,
};
use sea_orm::{
ConnectOptions, ConnectionTrait, DatabaseConnection, DatabaseTransaction, TransactionTrait,
};
use std::{sync::Arc, time::Duration};
#[derive(Clone)]
pub struct Database<C = DatabaseConnection> {
pool: Arc<C>,
pub identities: IdentityRepository<C>,
pub sessions: SessionRepository<C>,
pub review_contexts: ReviewContextRepository<C>,
pub workspace: WorkspaceRepository<C>,
pub comments: CommentRepository<C>,
pub mutations: MutationRepository<C>,
}
pub type Transaction = Database<DatabaseTransaction>;
impl<C: ConnectionTrait + TransactionTrait> Database<C> {
fn from_connection(connection: C) -> Self {
let pool = Arc::new(connection);
Self {
identities: IdentityRepository::new(Arc::clone(&pool)),
sessions: SessionRepository::new(Arc::clone(&pool)),
review_contexts: ReviewContextRepository::new(Arc::clone(&pool)),
workspace: WorkspaceRepository::new(Arc::clone(&pool)),
comments: CommentRepository::new(Arc::clone(&pool)),
mutations: MutationRepository::new(Arc::clone(&pool)),
pool,
}
}
}
impl Database {
pub async fn connect(url: &str) -> Result<Self> {
if !(url.starts_with("sqlite:")
|| url.starts_with("postgres:")
|| url.starts_with("postgresql:"))
{
return Err(invalid("Database URL must use sqlite: or postgres:"));
}
let _guard = if url.starts_with("sqlite:") && !url.contains(":memory:") {
let path = url
.trim_start_matches("sqlite:")
.trim_start_matches("//")
.split('?')
.next()
.unwrap_or_default();
let name = format!("{path}.migration.lock");
Some(
tokio::task::spawn_blocking(move || -> std::io::Result<std::fs::File> {
let f = std::fs::OpenOptions::new()
.create(true)
.read(true)
.write(true)
.truncate(false)
.open(name)?;
fs2::FileExt::lock_exclusive(&f)?;
Ok(f)
})
.await
.map_err(|e| invalid(e.to_string()))??,
)
} else {
None
};
let mut options = ConnectOptions::new(url);
options
.max_connections(if url.starts_with("sqlite:") { 1 } else { 8 })
.connect_timeout(Duration::from_secs(10))
.sqlx_logging(false);
let pool = sea_orm::Database::connect(options).await?;
if let Err(error) = super::migration::apply::<Migrator>(&pool).await {
let _ = pool.close().await;
return Err(error.into());
}
Ok(Self::from_connection(pool))
}
pub async fn close(&self) {
let _ = self.pool.as_ref().clone().close().await;
}
}
impl<C: ConnectionTrait> Database<C> {}
impl<C: ConnectionTrait + TransactionTrait<Transaction = DatabaseTransaction>> Database<C> {
pub async fn begin(&self) -> Result<Transaction> {
Ok(Database::from_connection(self.pool.begin().await?))
}
}
impl Database<DatabaseTransaction> {
fn into_transaction(self) -> Result<DatabaseTransaction> {
let Self {
pool,
identities,
sessions,
review_contexts,
workspace,
comments,
mutations,
} = self;
drop((
identities,
sessions,
review_contexts,
workspace,
comments,
mutations,
));
Arc::try_unwrap(pool)
.map_err(|_| invalid("Transaction still has repository handles in use"))
}
pub async fn commit(self) -> Result<()> {
Ok(self.into_transaction()?.commit().await?)
}
pub async fn rollback(self) -> Result<()> {
Ok(self.into_transaction()?.rollback().await?)
}
}